Skip to content

oc2: bound DAP lexer scan pointer against the strdup buffer end - #3375

Open
MarkLee131 wants to merge 1 commit into
Unidata:mainfrom
MarkLee131:fix/dap-lexer-oob
Open

oc2: bound DAP lexer scan pointer against the strdup buffer end#3375
MarkLee131 wants to merge 1 commit into
Unidata:mainfrom
MarkLee131:fix/dap-lexer-oob

Conversation

@MarkLee131

Copy link
Copy Markdown

Summary

daplex() in oc2/daplex.c advances its scan pointer past the trailing NUL of the strdup'd input buffer when it consumes a malformed token, then reads one byte past the buffer in the for-loop's (c=*p) condition. ASan flags a 1-byte heap-buffer-overflow READ at oc2/daplex.c:141 on a 3-byte malformed DAS/DDS body. The same post-buffer pointer is later stored into lexstate->next, so dap_parse_error()'s strlen(lexstate->next) (oc2/dapparse.c:454) walks further into adjacent heap and copies what it finds into the "context: ..." line printed to stderr.

Reachable through the public API by opening any dap://server/path URL whose DAS/DDS response contains the trigger bytes -- nc_open() -> NCD2_open -> dap_fetch -> DAPparse -> daplex.

Reproducer

A Python mock DAP server returning the 3-byte body is enough.

mock_dap_server.py:

import http.server, socketserver

PAYLOAD = b"\x99\x23\x8d"

class MockDAP(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.send_header("Content-Length", str(len(PAYLOAD)))
        self.end_headers()
        self.wfile.write(PAYLOAD)
    def log_message(self, *a, **k): pass

with socketserver.TCPServer(("127.0.0.1", 18080), MockDAP) as srv:
    srv.serve_forever()

poc.c:

#include <stdio.h>
#include <netcdf.h>
int main(void) {
    int ncid;
    int rc = nc_open("http://127.0.0.1:18080/data", 0, &ncid);
    fprintf(stderr, "nc_open returned %d\n", rc);
    if (rc == NC_NOERR) nc_close(ncid);
    return 0;
}
python3 mock_dap_server.py &
clang -fsanitize=address -g -O1 poc.c $(pkg-config --cflags --libs netcdf libcurl) -o poc
./poc

ASan output (current main):

==...==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000005974
READ of size 1 at 0x602000005974 thread T0
    #0 daplex                  oc2/daplex.c:141:39
    #1 dapparse                build/oc2/dapy.c:1494
    #2 DAPparse                oc2/dapparse.c:503
    #3 ocfetch                 oc2/ocinternal.c:236
    #4 oc_fetch                oc2/oc.c:129
    #5 dap_fetch               libdap2/daputil.c:851
    #6 fetchpatternmetadata    libdap2/ncd2dispatch.c:2071
    #7 NCD2_open               libdap2/ncd2dispatch.c:450
    #8 NC_open                 libdispatch/dfile.c:2262
    #9 nc_open                 libdispatch/dfile.c:700

0x602000005974 is located 0 bytes to the right of 4-byte region
allocated by thread T0 here:
    #0 strdup
    #1 daplexinit              oc2/daplex.c:343

Fix

Compute the strdup buffer's NUL position once in daplex, bound the outer for-loop with p<=end, and clamp p before assigning it back into lexstate->next so the secondary strlen in dap_parse_error also stays in-buffer.

Notes

oc2/dapparse.c:444-464 is git blamed to 2012-07-31, and the lexer loop has been similarly stable, so older release branches are likely affected too. I've only verified against current main.

daplex()'s main loop is

    for(p=lexstate->next; token==0 && (c=*p); p++)

Several inner branches use `c = *(++p)` and exit when c == '\0', so p
ends AT the trailing NUL of the strdup'd input.  The outer post-increment
then runs once more, leaving p ONE PAST the NUL, and the next (c=*p)
reads adjacent heap.  ASan flags a 1-byte heap-buffer-overflow at
oc2/daplex.c:141 on a 3-byte malformed DAS/DDS body delivered via
nc_open() against a DAP server.

The same post-buffer pointer is then stored into lexstate->next, so
dap_parse_error()'s strlen(lexstate->next) (oc2/dapparse.c:454) walks
further into adjacent heap and copies what it finds into the stderr
"context:" line.

Compute the strdup buffer end once, bound the outer loop with p<=end,
and clamp p before assigning lexstate->next so the secondary strlen
also stays in-buffer.
@CLAassistant

CLAassistant commented May 8, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants