Skip to content

Multiple memory-safety bugs found #112

Description

@DrWhax

ytnef Security Bug Report

This report documents several memory-safety bugs in the current master branch of ytnef with commit 5b24cfc12d7e3886b0c8853e6d8b2bb5a25ec8c1.

All dynamically confirmed issues below reproduce against code in lib/ytnef.c, not in the fuzz harness. The harness only provides attacker-controlled input to the library and then exercises the existing debug-printing path.

Environment

Build environment used for confirmation:

OS: Ubuntu 26.04 x86_64
Compiler: clang
Sanitizer: AddressSanitizer

Build Instructions

The following commands were used to build the ASan-instrumented target:

git clone https://github.com/Yeraze/ytnef/
cd ytnef
autoreconf -vfi
mkdir -p build-asan
cd build-asan
ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 \
../configure CC=clang CFLAGS='-O1 -g -fsanitize=address -fno-omit-frame-pointer' \
LDFLAGS='-fsanitize=address'
ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 make -j4

The reproducer binary used below is:

/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz

This harness is included in the tree at:

fuzz/ytnef_mapi_fuzz.c

It calls:

  1. TNEFFillMapi()
  2. MAPIPrint()

That means all crashes below occur in ytnef library code.

Bug 1: Heap Out-of-Bounds Read in MAPIPrint() via strlen() on Non-NUL-Terminated PT_STRING8

Affected Code

File:

lib/ytnef.c

Relevant lines:

  • MAPIPrint() prints a counted PT_STRING8 value with %.*s
  • then calls strlen((char*)mapidata->data)

Current code:

case PT_STRING8:
  printf("    Value: [%.*s]\n", mapidata->size, mapidata->data);
  if (strlen((char*)mapidata->data) != mapidata->size - 1) {
    printf("Detected Hidden data: [");
    ...
  }
  break;

Root Cause

TNEFFillMapi() stores PT_STRING8 property values as raw counted buffers. There is no guarantee that the buffer is NUL-terminated. MAPIPrint() later treats the same buffer as a C string by calling strlen(), causing a read past the heap allocation when the input does not contain a trailing '\0'.

This is the same bug pattern as the earlier project fix "Do not assume filename is null terminated", but in a different code path.

Security Impact

This is an attacker-controlled heap out-of-bounds read in the debug-print path. A malformed TNEF/MAPI property can crash the process when debug printing is enabled and MAPIPrint() is reached.

Reproducer

Create the reproducer:

printf '\x01\x00\x00\x00\x1e\x00\x37\x00\x01\x00\x00\x00\x04\x00\x00\x00ABCD' \
> /tmp/string8-no-nul.bin

Run:

ASAN_OPTIONS=detect_leaks=0 \
/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz < /tmp/string8-no-nul.bin

ASan Output

=================================================================
==58==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b872b5e0034 at pc 0x5660eb0aa86f bp 0x7fff1e41de50 sp 0x7fff1e41d618
READ of size 5 at 0x7b872b5e0034 thread T0
    #0 0x5660eb0aa86e in strlen (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x4686e) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x7f672c75b58f in MAPIPrint /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1474:15
    #2 0x5660eb179ce1 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:26:5
    #3 0x5660eb179ce1 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x7f672c22a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x7f672c22a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x5660eb0903a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

0x7b872b5e0034 is located 0 bytes after 4-byte region [0x7b872b5e0030,0x7b872b5e0034)
allocated by thread T0 here:
    #0 0x5660eb135b1d in calloc (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xd1b1d) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x7f672c759d1c in TNEFFillMapi /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:535:24
    #2 0x5660eb179cd5 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:25:7
    #3 0x5660eb179cd5 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x7f672c22a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x7f672c22a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x5660eb0903a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x4686e) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef) in strlen
==58==ABORTING

Suggested Remediation

Do not call strlen() on counted property data. If the code wants to detect trailing hidden data, it should operate within the declared mapidata->size only.

Bug 2: Heap Out-of-Bounds Read in DecompressRTF() Uncompressed Branch

Affected Code

File:

lib/ytnef.c

Relevant code:

if (magic == 0x414c454d) {
  dst = calloc(uncompressedSize, 1);
  ALLOCCHECK_CHAR(dst);
  memcpy(dst, src + 4, uncompressedSize);
  free(comp_Prebuf.data);
  return dst;
}

Root Cause

The uncompressed RTF path trusts uncompressedSize from attacker-controlled input but never verifies that the source buffer actually contains that many bytes. The only existing check is:

if (compressedSize != p->size - 4)

That validates the declared compressed size field against the outer container length, but it does not validate uncompressedSize before memcpy(dst, src + 4, uncompressedSize).

Security Impact

This is an attacker-controlled heap out-of-bounds read from the RTF property buffer. It is directly reachable through the decompression path.

Reproducer

Create the reproducer:

printf '\x01\x00\x00\x00\x02\x01\x09\x10\x01\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x40\x00\x00\x00MELA\x00\x00\x00\x00\x41\x42\x43\x44' \
> /tmp/rtf-uncompressed-overread.bin

Run:

ASAN_OPTIONS=detect_leaks=0 \
/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz < /tmp/rtf-uncompressed-overread.bin

ASan Output

=================================================================
==58==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6ff2197e0054 at pc 0x6054105cb59f bp 0x7fff98087ff0 sp 0x7fff980877b0
READ of size 64 at 0x6ff2197e0054 thread T0
    #0 0x6054105cb59e in __asan_memcpy (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xcf59e) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x73c21a9a209a in DecompressRTF /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1559:5
    #2 0x73c21a99d77d in MAPIPrint /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1454:39
    #3 0x605410611ce1 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:26:5
    #4 0x605410611ce1 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #5 0x73c21a42a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #6 0x73c21a42a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #7 0x6054105283a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

0x6ff2197e0054 is located 0 bytes after 20-byte region [0x6ff2197e0040,0x6ff2197e0054)
allocated by thread T0 here:
    #0 0x6054105cdb1d in calloc (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xd1b1d) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x73c21a99bd1c in TNEFFillMapi /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:535:24
    #2 0x605410611cd5 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:25:7
    #3 0x605410611cd5 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x73c21a42a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x73c21a42a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x6054105283a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xcf59e) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef) in __asan_memcpy
==58==ABORTING

Suggested Remediation

Before the memcpy(), validate:

uncompressedSize <= p->size - 4

If the stream does not contain enough bytes, reject it as malformed.

Bug 3: Heap Out-of-Bounds Read in DecompressRTF() on Truncated Back-Reference

Affected Code

File:

lib/ytnef.c

Relevant code:

flags = (flagCount++ % 8 == 0) ? src[in++] : flags >> 1;
if ((flags & 1) == 1) {
  unsigned int offset = src[in++];
  unsigned int length = src[in++];
  ...
}

Root Cause

The loop only checks in < p->size at the top. When a flag bit selects a back-reference, the code unconditionally reads two more bytes, but there is no check that two bytes are actually available. If only one byte remains, the second read goes out of bounds.

Security Impact

This is an attacker-controlled heap out-of-bounds read in compressed RTF parsing.

Reproducer

Create the reproducer:

printf '\x01\x00\x00\x00\x02\x01\x09\x10\x01\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x04\x00\x00\x00LZFu\x00\x00\x00\x00\x03\x00\x00\xff' \
> /tmp/rtf-backref-truncated.bin

Run:

ASAN_OPTIONS=detect_leaks=0 \
/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz < /tmp/rtf-backref-truncated.bin

ASan Output

=================================================================
==58==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x770ec3be0054 at pc 0x7adec4bd1534 bp 0x7fff000d2b10 sp 0x7fff000d2b08
READ of size 1 at 0x770ec3be0054 thread T0
    #0 0x7adec4bd1533 in DecompressRTF /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1580:31
    #1 0x7adec4bcc77d in MAPIPrint /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1454:39
    #2 0x60001590bce1 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:26:5
    #3 0x60001590bce1 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x7adec482a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x7adec482a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x6000158223a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

0x770ec3be0054 is located 0 bytes after 20-byte region [0x770ec3be0040,0x770ec3be0054)
allocated by thread T0 here:
    #0 0x6000158c7b1d in calloc (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xd1b1d) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x7adec4bcad1c in TNEFFillMapi /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:535:24
    #2 0x60001590bcd5 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:25:7
    #3 0x60001590bcd5 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x7adec482a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x7adec482a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x6000158223a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1580:31 in DecompressRTF
==58==ABORTING

Suggested Remediation

When a flag bit indicates a back-reference, validate that at least two bytes remain before consuming offset and length.

Bug 4: Heap Out-of-Bounds Read in MAPIPrint() via %s on Non-NUL-Terminated Decompressed RTF

Affected Code

Files:

lib/ytnef.c

Relevant code in MAPIPrint():

if ((vlTemp.data = (BYTE*)DecompressRTF(mapidata, &(vlTemp.size))) != NULL) {
  printf("%s\n", vlTemp.data);
  free(vlTemp.data);
}

Relevant allocation in DecompressRTF():

dst = calloc(uncompressedSize, 1);
...
return dst;

Root Cause

DecompressRTF() returns exactly uncompressedSize bytes and does not append a NUL terminator. MAPIPrint() then prints that byte buffer as a C string with %s, which causes a read beyond the allocation if the decompressed content is not NUL-terminated.

Security Impact

This is an attacker-controlled heap out-of-bounds read in the debug-print path for compressed RTF properties.

Reproducer

Create the reproducer:

printf '\x01\x00\x00\x00\x02\x01\x09\x10\x01\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x00LZFu\x00\x00\x00\x00\x00A\x42\x43' \
> /tmp/rtf-printf-no-nul.bin

Run:

ASAN_OPTIONS=detect_leaks=0 \
/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz < /tmp/rtf-printf-no-nul.bin

ASan Output

=================================================================
==58==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7253defe0031 at pc 0x5be748473e9b bp 0x7ffddcef9e50 sp 0x7ffddcef9620
READ of size 2 at 0x7253defe0031 thread T0
    #0 0x5be748473e9a in puts (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x4ee9a) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x7633e0097797 in MAPIPrint /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1455:15
    #2 0x5be74853ace1 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:26:5
    #3 0x5be74853ace1 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #4 0x7633dfc2a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #5 0x7633dfc2a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x5be7484513a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

0x7253defe0031 is located 0 bytes after 1-byte region [0x7253defe0030,0x7253defe0031)
allocated by thread T0 here:
    #0 0x5be7484f6b1d in calloc (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0xd1b1d) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)
    #1 0x7633e009c3b7 in DecompressRTF /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1609:11
    #2 0x7633e009777d in MAPIPrint /home/fuzz/dev/ytnef/build-asan/lib/../../lib/ytnef.c:1454:39
    #3 0x5be74853ace1 in run_one /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:26:5
    #4 0x5be74853ace1 in main /home/fuzz/dev/ytnef/build-asan/fuzz/../../fuzz/ytnef_mapi_fuzz.c:69:3
    #5 0x7633dfc2a600 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
    #6 0x7633dfc2a717 in __libc_start_main csu/../csu/libc-start.c:360:3
    #7 0x5be7484513a4 in _start (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x2c3a4) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/fuzz/dev/ytnef/build-asan/fuzz/.libs/ytnef_mapi_fuzz+0x4ee9a) (BuildId: ca71affe7db3137eb190ddd32a4a0445e8f086ef) in puts
==58==ABORTING

Suggested Remediation

There are two straightforward options:

  1. allocate uncompressedSize + 1 and explicitly append '\0' in DecompressRTF()
  2. stop using %s and print the decompressed data with length-aware logic

The second option is safer for binary-adjacent data paths.

Additional Issue Found by Static Audit: Big-Endian Out-of-Bounds Read in Byte-Swap Helpers

Affected Code

File:

lib/ytnef.c

Affected functions:

  • SwapWord()
  • SwapDWord()
  • SwapDDWord()

Root Cause

Under WORDS_BIGENDIAN, each helper uses:

converter.bytes[i] = p[correct - i];

On the first iteration this reads p[correct], which is one byte past the end of the valid range. The index should be:

p[correct - i - 1]

Status

This issue was not dynamically reproduced on the current host because the test machine is little-endian x86_64, so the big-endian branch is not executed here. It still appears to be a real parser bug on big-endian targets.

I have not tried to reproduce this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions