JakeChampion / JakeChampion/trafficserver
[audit][cache] Doc::len read from disk is trusted as the object length without bounding to the read buffer, enabling out-of-bounds reads
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 0
- Forks
- 0
- Avg merge
- 8h 2m
- Merged PRs (30d)
- 21
Description
Severity: medium · Category: memory-bug
Location: src/iocore/cache/CacheVC.cc:406
What's wrong
In handleReadDone the read buffer is allocated to dir_approx_size(&dir) (via do_read_call setting io.aiocb.aio_nbytes, then handleRead allocating buf = new_IOBufferData(iobuffer_size_to_index(io.aiocb.aio_nbytes,...)) at CacheVC.cc:521). After the read, the only integrity gates are stripe->dir_valid(&dir), doc->magic == DOC_MAGIC, and the version check. Nothing validates that doc->len (a value taken verbatim from the on-disk Doc) is <= the allocated buffer. The checksum loop then walks b < (char*)doc + doc->len and the RAM-cache insertion (stripe->ram_cache->put(read_key, buf.get(), doc->len, ...) at CacheVC.cc:442, which memcpys doc->len bytes when copy/compress is enabled) both dereference up to doc->len bytes. The same unbounded trust reappears in openReadMain (bytes = doc->len - doc_pos; new_IOBufferBlock(buf, bytes, doc_pos) at CacheRead.cc:567/697), which would hand out-of-bounds bytes to the client read buffer. A corrupted/torn Doc whose 4-byte magic still equals DOC_MAGIC (e.g. from a partial aggregation write or an adjacent object bleed) produces an over-large len and drives an OOB read. dir_approx_size only bounds the fragment's rounded size, not doc->len.
Evidence
for (char *b = doc->hdr(); b < reinterpret_cast<char *>(doc) + doc->len; b++) {
checksum += *b;
} // CacheVC.cc:406-408, buf sized to io.aiocb.aio_nbytes = dir_approx_size(&dir)
Suggested fix
After confirming DOC_MAGIC, validate doc->len against io.aiocb.aio_nbytes (and against sizeof(Doc)+doc->hlen for the header) before running the checksum loop, ram_cache->put, or building IOBufferBlocks; treat an out-of-range len as DOC_CORRUPT and remove the dir entry, as is already done for other corruption cases.
Filed from an automated multi-lens codebase audit. Full report: CODEBASE_AUDIT.md / audit-report.html on branch claude/codebase-audit-review-9nw7vz.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/iocore/cache/CacheVC.cc at handleReadDone around lines 406 and 442, then trace the buffer sizing in do_read_call and handleRead around line 521. Review openReadMain in src/iocore/cache/CacheRead.cc around lines 567 and 697, along with existing DOC_CORRUPT handling. Done means an out-of-range Doc length is rejected before checksum, RAM-cache insertion, or IOBufferBlock construction and the directory entry follows the existing corruption path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100