fix: do not walk past parent NUL in make_parents_safely - #31
Conversation
file.write createParents walks ancestors starting at parent + root_len + 1. When the parent is the configured root (a file written directly under /sd after the card is gone), that pointer is one past the strlcpy NUL and scans the PATH_MAX tail, then past the array if no NUL remains. Skip the walk when strlen(parent) <= root_len and only mkdir/stat the parent path. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs real behavior proof before merge. Reviewed September 7, 2026, 10:38 AM ET / 14:38 UTC. ClawSweeper reviewWhat this changesThe PR prevents room-node file writes from scanning past the parent path’s terminating NUL when creating a missing storage root, and adds focused helper tests. Merge readiness⛔ Blocked before merge - 2 items remain The fix remains necessary on current main, and no introduced correctness defect was found. The existing request for production-path behavior proof remains unresolved. Priority: P2 Review scores
Verification
How this fits togetherThe room-node file subsystem accepts registered file commands for a board-provided storage root. It validates paths, creates requested parent directories, and writes bounded file content or returns a typed error. flowchart TD
A[Registered file write] --> B[Validate path and content]
B --> C{Parent missing and creation enabled?}
C -->|Yes| D[Guard parent directory walk]
D --> E[Check canonical containment]
C -->|No| E
E --> F[Write file or return error]
Before merge
Agent review detailsSecurityNone. Review metricsNone. Technical reviewBest possible solution: Retain the narrow bounds guard and demonstrate safe completion or a typed filesystem error through the production file.write path after storage-root loss. Do we have a high-confidence way to reproduce the issue? Yes, from source: after successful storage registration, remove the root and request a direct-child file.write with createParents enabled; current main starts reading beyond the parent NUL. No production reproduction was executed during this read-only review. Is this the best way to solve the issue? Yes. The guard removes the invalid walk while preserving nested parent creation, final directory checks, and existing path validation; the added helper follows the component’s existing host-testable validation pattern. AGENTS.md: not found in the target repository. Codex review notes: model internal, reasoning medium; reviewed against 3294af3aaa94. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (17 earlier review cycles; latest 8 shown)
|
What Problem This Solves
file.writewithcreateParentscallsmake_parents_safelyon the destination parent. The walk started atparent + root_len + 1. When the file sits directly under the configured root (/sd/note.txtbecomes parent/sd), that pointer is one byte past thestrlcpyNUL.The loop then reads the uninitialized
PATH_MAXtail. If that tail has no later NUL, the next read is past the array. This happens when the card is missing afterroom_files_register_node_commandsalready stored the root string, solstatof the parent fails andcreateParentstries to rebuild it.Evidence
On current
main, the walk has no length guard:Host compile of that loop on a
PATH_MAXheap buffer (/sdplusXfill, no second NUL):Same loop under AddressSanitizer (original
for (*p != '\0')with no bounds check):After this patch,
room_file_parent_walk_startreturns NULL whenstrlen(parent) <= strlen(root).make_parents_safelyskips the walk and onlylstat/mkdirs the parent path.Why This Change Was Made
The walk is only needed for ancestors deeper than the configured root. When the parent is the root, the existing final
lstat/mkdirofparentis the whole job. Starting atroot_len + 1in that case is not a walk; it is an out-of-bounds read.User Impact
file.writewithcreateParentsto a file directly under the public root no longer reads past the parent string when that root directory is missing (unmounted card, path removed after register). Nested parents still walkroot_len + 1onward. Preflight (preflightOnly) is unchanged because it never callsmake_parents_safely.Real behavior proof
Behavior or issue addressed:
make_parents_safelystarted the createParents walk one byte past the parent NUL when the parent equaled the configured root, scanning the PATH_MAX tail and then past the array.Real environment tested: macOS 15, Apple clang, host checkout of
openclaw/esp-openclaw-nodeatupstream/main6f5c8e8plus this branch. PATH_MAX is 1024. ESP-IDF is not installed on this machine, so firmware is not flashed here.Exact steps or command run after this patch: Compiled the unfixed walk (
cc /tmp/f002-walk-unfixed.candcc -fsanitize=address /tmp/f002-walk-asan.c), then the patched skip plus mkdir-only path (cc /tmp/f002-walk-fixed.c), then the host validation program already wired in CI.Evidence after fix: terminal output from the patched tree:
Observed result after fix: Parent
/sdwith root/sdno longer starts a walk (0 reads). The missing root is created by the existing mkdir/stat ofparentonly. AddressSanitizer overflow on the old loop is gone on the patched path.What was not tested: Flashing a room-node firmware image and invoking
file.writeover the gateway against a physically unmounted SD card. Nested parent creation on-device (the host case/sd/a/bstill returns walk startparent + 4).Related
17837ce, 2026-08-07).