diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 75dc6476a9b..ca3561db7dc 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -513,7 +513,7 @@ @ stdcall RtlDecodePointer(ptr) # @ stub RtlDecodeSystemPointer @ stdcall RtlDecompressBuffer(long ptr long ptr long ptr) -@ stub RtlDecompressFragment +@ stdcall RtlDecompressFragment(long ptr long ptr long long ptr ptr) @ stub RtlDefaultNpAcl @ stub RtlDelete @ stdcall RtlDeleteAce(ptr long) diff --git a/dlls/ntdll/rtl.c b/dlls/ntdll/rtl.c index d9e448af4e7..4aef1eeefaa 100644 --- a/dlls/ntdll/rtl.c +++ b/dlls/ntdll/rtl.c @@ -1309,17 +1309,221 @@ NTSTATUS WINAPI RtlCompressBuffer(USHORT format, PUCHAR uncompressed, ULONG unco } } +/* decompress a single LZNT1 chunk */ +static UCHAR *lznt1_decompress_chunk(UCHAR *dst, ULONG dst_size, UCHAR *src, ULONG src_size) +{ + UCHAR *src_cur = src, *src_end = src + src_size; + UCHAR *dst_cur = dst, *dst_end = dst + dst_size; + ULONG displacement_bits, length_bits; + ULONG code_displacement, code_length; + WORD flags, code; + + while (src_cur < src_end && dst_cur < dst_end) + { + flags = 0x8000 | *src_cur++; + while ((flags & 0xff00) && src_cur < src_end) + { + if (flags & 1) + { + /* backwards reference */ + if (src_cur + sizeof(WORD) > src_end) + return NULL; + + code = *(WORD *)src_cur; + src_cur += sizeof(WORD); + + /* find length / displacement bits */ + for (displacement_bits = 12; displacement_bits > 4; displacement_bits--) + if ((1 << (displacement_bits - 1)) < dst_cur - dst) break; + + length_bits = 16 - displacement_bits; + code_length = (code & ((1 << length_bits) - 1)) + 3; + code_displacement = (code >> length_bits) + 1; + + if (dst_cur < dst + code_displacement) + return NULL; + + /* copy bytes of chunk - we can't use memcpy() since source and dest can + * be overlapping, and the same bytes can be repeated over and over again */ + while (code_length--) + { + if (dst_cur >= dst_end) return dst_cur; + *dst_cur = *(dst_cur - code_displacement); + dst_cur++; + } + } + else + { + /* uncompressed data */ + if (dst_cur >= dst_end) return dst_cur; + *dst_cur++ = *src_cur++; + } + flags >>= 1; + } + } + + return dst_cur; +} + +/* decompress data encoded with LZNT1 */ +static NTSTATUS lznt1_decompress(UCHAR *dst, ULONG dst_size, UCHAR *src, ULONG src_size, + ULONG offset, ULONG *final_size, UCHAR *workspace) +{ + UCHAR *src_cur = src, *src_end = src + src_size; + UCHAR *dst_cur = dst, *dst_end = dst + dst_size; + ULONG chunk_size, block_size; + WORD chunk_header; + UCHAR *ptr; + + if (src_cur + sizeof(WORD) > src_end) + return STATUS_BAD_COMPRESSION_BUFFER; + + /* skip over chunks with a distance >= 0x1000 to the destination offset */ + while (offset >= 0x1000 && src_cur + sizeof(WORD) <= src_end) + { + chunk_header = *(WORD *)src_cur; + src_cur += sizeof(WORD); + if (!chunk_header) goto out; + chunk_size = (chunk_header & 0xfff) + 1; + + if (src_cur + chunk_size > src_end) + return STATUS_BAD_COMPRESSION_BUFFER; + + src_cur += chunk_size; + offset -= 0x1000; + } + + /* handle partially included chunk */ + if (offset && src_cur + sizeof(WORD) <= src_end) + { + chunk_header = *(WORD *)src_cur; + src_cur += sizeof(WORD); + if (!chunk_header) goto out; + chunk_size = (chunk_header & 0xfff) + 1; + + if (src_cur + chunk_size > src_end) + return STATUS_BAD_COMPRESSION_BUFFER; + + if (dst_cur >= dst_end) + goto out; + + if (chunk_header & 0x8000) + { + /* compressed chunk */ + if (!workspace) return STATUS_ACCESS_VIOLATION; + ptr = lznt1_decompress_chunk(workspace, 0x1000, src_cur, chunk_size); + if (!ptr) return STATUS_BAD_COMPRESSION_BUFFER; + if (ptr - workspace > offset) + { + block_size = min((ptr - workspace) - offset, dst_end - dst_cur); + memcpy(dst_cur, workspace + offset, block_size); + dst_cur += block_size; + } + } + else + { + /* uncompressed chunk */ + if (chunk_size > offset) + { + block_size = min(chunk_size - offset, dst_end - dst_cur); + memcpy(dst_cur, src_cur + offset, block_size); + dst_cur += block_size; + } + } + + src_cur += chunk_size; + } + + /* handle remaining chunks */ + while (src_cur + sizeof(WORD) <= src_end) + { + chunk_header = *(WORD *)src_cur; + src_cur += sizeof(WORD); + if (!chunk_header) goto out; + chunk_size = (chunk_header & 0xfff) + 1; + + if (src_cur + chunk_size > src_end) + return STATUS_BAD_COMPRESSION_BUFFER; + + /* fill space with padding when the previous chunk was decompressed + * to less than 4096 bytes. no padding is needed for the last chunk + * or when the next chunk is truncated */ + block_size = ((dst_cur - dst) + offset) & 0xfff; + if (block_size) + { + block_size = 0x1000 - block_size; + if (dst_cur + block_size >= dst_end) + goto out; + memset(dst_cur, 0, block_size); + dst_cur += block_size; + } + + if (dst_cur >= dst_end) + goto out; + + if (chunk_header & 0x8000) + { + /* compressed chunk */ + dst_cur = lznt1_decompress_chunk(dst_cur, dst_end - dst_cur, src_cur, chunk_size); + if (!dst_cur) return STATUS_BAD_COMPRESSION_BUFFER; + } + else + { + /* uncompressed chunk */ + block_size = min(chunk_size, dst_end - dst_cur); + memcpy(dst_cur, src_cur, block_size); + dst_cur += block_size; + } + + src_cur += chunk_size; + } + +out: + if (final_size) + *final_size = dst_cur - dst; + + return STATUS_SUCCESS; + +} + +/****************************************************************************** + * RtlDecompressFragment [NTDLL.@] + */ +NTSTATUS RtlDecompressFragment(USHORT format, PUCHAR uncompressed, ULONG uncompressed_size, + PUCHAR compressed, ULONG compressed_size, ULONG offset, + PULONG final_size, PVOID workspace) +{ + TRACE("0x%04x, %p, %u, %p, %u, %u, %p, %p\n", format, uncompressed, + uncompressed_size, compressed, compressed_size, offset, final_size, workspace); + + switch (format & ~COMPRESSION_ENGINE_MAXIMUM) + { + case COMPRESSION_FORMAT_LZNT1: + return lznt1_decompress(uncompressed, uncompressed_size, compressed, + compressed_size, offset, final_size, workspace); + + case COMPRESSION_FORMAT_NONE: + case COMPRESSION_FORMAT_DEFAULT: + return STATUS_INVALID_PARAMETER; + + default: + FIXME("format %u not implemented\n", format); + return STATUS_UNSUPPORTED_COMPRESSION; + } +} + + /****************************************************************************** * RtlDecompressBuffer [NTDLL.@] */ -NTSTATUS WINAPI RtlDecompressBuffer(USHORT CompressionFormat, PUCHAR UncompressedBuffer, - ULONG UncompressedBufferSize, PUCHAR CompressedBuffer, - ULONG CompressedBufferSize, PULONG FinalUncompressedSize) +NTSTATUS WINAPI RtlDecompressBuffer(USHORT format, PUCHAR uncompressed, ULONG uncompressed_size, + PUCHAR compressed, ULONG compressed_size, PULONG final_size) { - FIXME("0x%04x, %p, %u, %p, %u, %p :stub\n", CompressionFormat, UncompressedBuffer, UncompressedBufferSize, - CompressedBuffer, CompressedBufferSize, FinalUncompressedSize); + TRACE("0x%04x, %p, %u, %p, %u, %p\n", format, uncompressed, + uncompressed_size, compressed, compressed_size, final_size); - return STATUS_NOT_IMPLEMENTED; + return RtlDecompressFragment(format, uncompressed, uncompressed_size, + compressed, compressed_size, 0, final_size, NULL); } /*********************************************************************** diff --git a/dlls/ntdll/tests/rtl.c b/dlls/ntdll/tests/rtl.c index c6a7023be1f..99fd4130136 100644 --- a/dlls/ntdll/tests/rtl.c +++ b/dlls/ntdll/tests/rtl.c @@ -1663,11 +1663,8 @@ static void test_RtlCompressBuffer(void) memset(buf2, 0x11, sizeof(buf2)); status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf2, sizeof(buf2), buf1, buf_size, &final_size); - todo_wine ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status); - todo_wine ok(final_size == sizeof(test_buffer), "got wrong final_size %u\n", final_size); - todo_wine ok(!memcmp(buf2, test_buffer, sizeof(test_buffer)), "got wrong decoded data\n"); ok(buf2[sizeof(test_buffer)] == 0x11, "too many bytes written\n"); diff --git a/dlls/ntoskrnl.exe/ntoskrnl.exe.spec b/dlls/ntoskrnl.exe/ntoskrnl.exe.spec index 11d1c626ce9..0bb7d0413eb 100644 --- a/dlls/ntoskrnl.exe/ntoskrnl.exe.spec +++ b/dlls/ntoskrnl.exe/ntoskrnl.exe.spec @@ -977,7 +977,7 @@ @ stub RtlCustomCPToUnicodeN @ stdcall RtlDecompressBuffer(long ptr long ptr long ptr) ntdll.RtlDecompressBuffer @ stub RtlDecompressChunks -@ stub RtlDecompressFragment +@ stdcall RtlDecompressFragment(long ptr long ptr long long ptr ptr) ntdll.RtlDecompressFragment @ stub RtlDelete @ stdcall RtlDeleteAce(ptr long) ntdll.RtlDeleteAce @ stdcall RtlDeleteAtomFromAtomTable(ptr long) ntdll.RtlDeleteAtomFromAtomTable