busybox: awk next_token(): heap-buffer-overflow (2nd OOB read in the same function; related to CVE-2023-42366; confirmed in all releases since 1.36.0)
Ediz Yiğit via busybox <[email protected]>
| Newsgroups | gmane.linux.busybox |
|---|---|
| Message-ID | <CADe5X_N5ctD4o=o5h=jdU=SfGaP_Bp+H2UVuiJ-5RwmFbN-xsA@mail.gmail.com> |
Hello,
we found a 1-byte heap out-of-bounds read in awk's tokenizer, editors/awk.c
next_token() at line ~1207. The trigger differs from the previously reported
CVE-2023-42366 (next_token, awk.c:1159 in 1.36.1), but it is the same bug class
(out-of-bounds read while scanning a string token) in the same function.
Affected versions (all verified from clean source with a proper ASan build):
master (7473045ad) and release tags 1.36.0, 1.36.1, 1.37.0, 1.38.0. 1.35.0 was
rebuilt and tested with the same protocol: it contains the same
tokenizer code but
the tested triggers did not reproduce the crash, so 1.36.0 is the oldest release
we claim as affected.
Root cause: in nextchar() (editors/awk.c), an unrecognized \z escape whose z is
newline is "eaten" by advancing and retrying (goto again). When the awk program
ends with \<newline> right after a string token, that retry reads the
terminating
NUL appended to the program buffer and advances one byte past it; the
string-scan
loop in next_token() then dereferences that byte:
while (*p != '"') { /* OOB read: p one byte past buffer end */
if (*p == '\0' || *p == '\n')
syntax_error(EMSG_UNEXP_EOS);
...
}
Trigger (program file, 32 bytes, trailing backslash on last line):
BEGIN { print "ht "hi" "hi" }\
(newline)
ASan report (deterministic):
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1 ... located 0 bytes after 33-byte region
#0 next_token editors/awk.c:1207:11
Impact: Low on stock glibc builds (read hits padding; only a benign
syntax error is
emitted); DoS on ASan/instrumented builds. No write primitive. Build
note: BusyBox
only honors ASan via .config CONFIG_EXTRA_CFLAGS="-fsanitize=address", not via a
CFLAGS_EXTRA make variable. Verification: every listed version was
rebuilt from a
fresh clean worktree with ASan enabled, and ASan presence was
confirmed per build
(nm); all five crash on the identical input at the same code path.
A verified fix is included: do not advance past a NUL in nextchar() (stop and
return it). Rebuilt with ASan: the trigger now fails cleanly with
"Unexpected end
of string"; the BusyBox awk testsuite shows no new failures (the same 3
pre-existing ASan-build failures occur with and without the fix). Full patch:
0001-awk-fix-heap-oob.patch.
Discovered with AFL++ (LLVM mode, clang-16, ASan build) targeting
busybox awk -f.
Given this is a second OOB read in the same function previously tracked as
CVE-2023-42366, we would like to request a CVE for this finding, or, if it is
already tracked, please let us know the reference. Happy to provide the exact
trigger and verification data on request.
PoC and full details: attachment disclosure-awk-next_token-heap-oob.md.
Best regards
Ediz Yiğit
[email protected]
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox
disclosure-awk-next_token-heap-oob.md
(text/markdown, 9.9 KB)
# Responsible Disclosure — BusyBox awk `next_token()` — 1-byte heap out-of-bounds read
> **STATUS: VERIFIED (2026-08-20).** This finding was briefly marked "retracted":
> that decision was based on tests with fresh builds that did NOT contain ASan (the
> `CFLAGS_EXTRA` build flag is not honored by BusyBox; the correct way is
> `CONFIG_EXTRA_CFLAGS="-fsanitize=address"` via `.config`). With a proper ASan build
> from clean source, the crash reproduces deterministically — **the bug is real and
> currently present in master.**
- **Report ID:** VULNLAB-FD-002
- **Date:** 2026-08-19
- **Affected project:** BusyBox (https://busybox.net)
- **Affected component:** `awk` applet, `editors/awk.c`, function `next_token()`
- **Affected version:** BusyBox git master (verified at commit `7473045ad`) **and all
releases verified: 1.36.0, 1.36.1, 1.37.0, 1.38.0** (each rebuilt from a fresh tag
worktree with a proper ASan build; identical trigger crashes in every one).
1.35.0 was also rebuilt and tested with the same protocol: it contains the same
tokenizer code, but the tested triggers did not reproduce the crash,
so releases older than 1.36.0 are **not claimed** as affected.
- **CWE:** CWE-125 (Out-of-bounds Read)
- **Severity:** Low (local DoS on sanitizer builds; 1-byte OOB heap read)
---
## Summary
`next_token()` reads one byte past the end of the heap buffer holding the awk program text
when any string token's scan reaches a trailing backslash at the very end of the program,
i.e. the program ends with `\<newline>` while a string is being parsed. Under ASan this is
a deterministic `heap-buffer-overflow` (READ of size 1) at `editors/awk.c:1207`. The bug is
a **second,
independently-triggered OOB read in the same function** that was previously reported as
CVE-2023-42366 (heap-buffer-overflow in `next_token`, awk.c:1159 in 1.36.1) — the same bug
class (out-of-bounds read while scanning a string token), reached through a different
mechanism (escape handling in `nextchar()`, not name-token termination).
## Root cause
`editors/awk.c`, `nextchar()` (used by the string-token branch of `next_token()`).
For an unrecognized escape `\z` whose `z` is newline, `nextchar()` "eats" the
newline and retries the read:
```c
static char nextchar(char **s)
{
char c, *pps;
again:
c = *(*s)++;
pps = *s;
if (c == '\\')
c = bb_process_escape_sequence((const char**)s);
if (c == '\\' && *s == pps) { /* unrecognized \z? */
c = *(*s); /* yes, fetch z */
if (c) { /* advance unless z = NUL */
(*s)++;
if (c == '\n') /* \<newline>? eat it */
goto again; /* <-- re-read, may go past buffer end */
}
}
return c;
}
```
The program buffer is exactly the file content plus the NUL appended by
`xmalloc_read_with_initial_buf()`. When the awk program ends with `\<newline>` right
after a string token:
1. `nextchar()` consumes the `\`, then the `\<newline>` "eat" path advances past the
newline and `goto again`.
2. The retry reads the appended NUL and advances `*s` one byte past the buffer.
3. Back in the string branch of `next_token()`, `p = pp` is now one byte past the
buffer, and the loop condition reads out of bounds:
```c
} else if (*p == '"') {
char *s = t_string = ++p;
while (*p != '"') { /* line 1207: OOB READ */
if (*p == '\0' || *p == '\n')
syntax_error(EMSG_UNEXP_EOS);
...
}
```
## Trigger / Proof of concept
`poc/awk_next_token_oob.awk`:
```
BEGIN { print "ht "hi" "hi" }\
```
Note: requires the program to end with a trailing backslash, so that the string-scan
loop reaches a `\<newline>` right at the buffer end and the escape-eat path in
`nextchar()` runs past the appended NUL. No specific token structure is needed —
the same crash is reached with a 10-byte program `x = "abc\` + newline (no name
token immediately followed by a quote); the trigger just needs the string scan to
end at the trailing `\<newline>`. Reproduce:
```
$ busybox awk -f poc/awk_next_token_oob.awk
```
### ASan report (proper ASan build from clean source, deterministic, all runs crash):
```
==3612484==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x504000000271 ...
READ of size 1 at 0x504000000271 thread T0
#0 next_token editors/awk.c:1207:11
0x504000000271 is located 0 bytes after 33-byte region [0x504000000250,0x504000000271)
allocated by thread T0 here:
#0 in realloc
#1 in xrealloc libbb/xfuncs_printf.c:61
```
Build notes (important): the applet must be built with ASan enabled the way BusyBox
actually honors it — `.config` option `CONFIG_EXTRA_CFLAGS="-fsanitize=address
-fno-omit-frame-pointer"` (BusyBox does NOT honor a plain `CFLAGS_EXTRA` make
variable for applet objects). Rebuilt tags 1.36.0/1.36.1/1.37.0/1.38.0 this way:
the identical input crashes every one of them at the same code path.
Discovered by coverage-guided fuzzing (AFL++ / LLVM mode, clang-16, ASan-instrumented
busybox) targeting `busybox awk -f @@` (crash id `awk_out/awkmaster/crashes/id:000000`).
### Verification matrix (each release rebuilt from a fresh clean worktree, defconfig +
CONFIG_EXTRA_CFLAGS="-fsanitize=address -fno-omit-frame-pointer", gcc 13.3, SKIP_STRIP=y;
ASan presence confirmed per build via `nm busybox | grep -c __asan_` = 33-35 symbols)
| Version | OOB line in next_token | ASan error | Crash |
|---------|------------------------|------------|-------|
| 1.35.0 | — | not triggered | **no** (tested, same tokenizer code; trigger not reproducible) |
| 1.36.0 | awk.c:1147 | heap-buffer-overflow, READ 1 | yes |
| 1.36.1 | awk.c:1147 | heap-buffer-overflow, READ 1 | yes |
| 1.37.0 | awk.c:1216 | heap-buffer-overflow, READ 1 | yes |
| 1.38.0 | awk.c:1210 | heap-buffer-overflow, READ 1 | yes |
| master | awk.c:1207 | heap-buffer-overflow, READ 1 | yes |
All five crashing versions crash on the identical input with an **identical full ASan
message** — READ of size 1 at the same address, `located 0 bytes after 33-byte region`,
allocation via `xrealloc`→`realloc`, in function `next_token` — same root cause.
Symbolized master trace: `#0 next_token awk.c:1207:11`, `#1 parse_expr awk.c:1426:17`,
allocation `#1 xrealloc libbb/xfuncs_printf.c:61:8`.
### Transparency note
An interim "retracted" status was recorded after tests with builds that did not
actually contain ASan (`make CFLAGS_EXTRA=...` is not honored by BusyBox for applet
objects; `CONFIG_EXTRA_CFLAGS` is). Once built correctly, the crash reproduced from
clean source and the retraction was withdrawn. No email was sent during that period.
## Impact
- **Local DoS:** on ASan/valgrind-instrumented builds the crafted script aborts the awk
process deterministically (SIGABRT, exit 134). On stock glibc builds the 1-byte read lands
in allocator padding (glibc rounds a 32-byte request to 40 usable bytes), so the scan
terminates on the first NUL/space byte and the only observable result is a benign
`Unexpected end of string` syntax error — **no crash, no leak observed** on standard builds.
- **Information disclosure:** limited to a single heap byte; only observable if that byte is
non-NUL and non-whitespace AND awk output is subsequently used in a security-sensitive
comparison. Not demonstrated.
- **No write primitive:** in the string tokenizer `s <= p` always holds (each `nextchar()`
consumes at least as many input bytes as it produces), so the in-place unescape cannot
outrun the read pointer; the defect is read-only.
- **Reachability:** requires an attacker to supply a crafted awk *program* (e.g. a config or
script parsed with `awk -f`), which is a local/trusted-input scenario in most deployments.
Because stock builds show no observable effect, severity is assessed as **Low**. It is still
worth reporting because (a) it is an unpatched defect in the latest master, (b) it is a
repeat of the CVE-2023-42366 bug family in the same function, indicating the 2021 rewrite
did not fully close the class, and (c) sanitizer-flagged builds (CI, embedded dev images)
will crash.
## Suggested fix (verified)
Root cause of the OOB read: when the awk program ends with `\<newline>` right after a
string token, `nextchar()` eats the newline (`goto again`) and then, on the next
iteration, reads the terminating NUL that `xmalloc_read_with_initial_buf()` appends and
advances one byte past the end of the buffer. The string-scan loop in `next_token()`
(`while (*p != '"')`, awk.c:1207) then dereferences that byte.
Fix: never advance past a NUL in `nextchar()` — stop at it and return it; the string
branch already reports "Unexpected end of string" when it sees NUL.
```c
static char nextchar(char **s)
{
char c, *pps;
again:
- c = *(*s)++;
+ c = *(*s);
+ if (c == '\0')
+ return c; /* do not advance past the terminating NUL */
+ (*s)++;
pps = *s;
...
```
Verified: rebuilt master + this change with a proper ASan build; the 32-byte trigger
now fails cleanly with `awk: ...: Unexpected end of string` (no ASan crash). The
BusyBox awk testsuite shows no new failures (the 3 pre-existing ASan-build failures
are identical with and without the fix). Full patch:
`0001-awk-fix-heap-oob.patch` (git format-patch).
## Disclosure timeline
- 2026-08-19: Discovered via AFL++ fuzzing; ASan-verified deterministic on the fuzz build.
- 2026-08-20: Verified from clean source with a proper ASan build
(`CONFIG_EXTRA_CFLAGS="-fsanitize=address"`); confirmed in master and in rebuilt
release tags 1.36.0, 1.36.1, 1.37.0, 1.38.0. Normal-build impact assessed (none).
(An interim "retract" note was added after testing with non-ASan builds that did not
honor `CFLAGS_EXTRA`; it was removed once the correct ASan build reproduced the crash.)
- *TBD:* Send to BusyBox developers ([email protected]) with reference to CVE-2023-42366.
## References
- CVE-2023-42366 — heap-buffer-overflow in `next_token`, BusyBox 1.36.1 (awk.c:1159)
- BusyBox commit `4f27503a1` — "awk: get rid of 'move name one char back' trick in next_token()"
(introduced the `g_saved_ch` mechanism)
- Function `next_token`: `editors/awk.c`
- CWE-125: https://cwe.mitre.org/data/definitions/125.html
0001-awk-fix-heap-oob.patch
(text/x-patch, 1.5 KB)
From 6c945e6cb18235678df0059cd091b6117a64d1e1 Mon Sep 17 00:00:00 2001 From: Ediz <[email protected]> Date: Thu, 20 Aug 2026 01:45:36 +0300 Subject: [PATCH] awk: fix 1-byte heap-buffer-overflow in next_token() string parse When the awk program ends with \<newline> right after the last string token, nextchar() eats the newline and then reads the terminating NUL that xmalloc_read_with_initial_buf() appends, advancing past the end of the buffer. The string-scan loop in next_token() then dereferences one byte past the buffer (heap-buffer-overflow, READ of size 1). Do not advance past a NUL in nextchar(): stop at it and return it. The caller (next_token string branch) already reports "Unexpected end of string" when it sees NUL. Reproducer (32 bytes, ASan build): BEGIN { print "ht "hi" "hi" }\ crashes on master (editors/awk.c:1207) and releases 1.36.0-1.38.0 at the corresponding OOB read in next_token() (see disclosure report for per-version line numbers); with this fix it fails cleanly with a syntax error. --- editors/awk.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/editors/awk.c b/editors/awk.c index 42a1c3ceb..a91a90611 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -894,7 +894,10 @@ static char nextchar(char **s) { char c, *pps; again: - c = *(*s)++; + c = *(*s); + if (c == '\0') + return c; /* do not advance past the terminating NUL */ + (*s)++; pps = *s; if (c == '\\') c = bb_process_escape_sequence((const char**)s); -- 2.43.0
awk_next_token_oob.awk
(application/x-awk, 32 B) - not displayed