Repository navigation
permission: make inserting an already-granted path idempotent - #66929
itxcrusher wants to merge 1 commit into
Conversation
|
Review requested:
|
|
Welcome to Node.js, and thank you for your first contribution! Before review, please take a moment to read:
Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal. Caution AgentScan found account activity patterns that may be consistent with automation. This is a heuristic, not proof that this pull request was opened by an agent or violates policy. AI-assisted contributions are permitted, but automated tooling must not open pull requests without advance approval, and contributors must personally understand, test, verify, and take responsibility for every submitted change. See the AgentScan analysis, AI use policy, and automation policy for additional context. |
`Node::CreateChild` treats an empty remaining prefix as reachable only when the node is not yet a leaf. Inserting a second grant that is identical up to and including its first `*` walks to the existing node, marks it a leaf, and recurses with an empty prefix. The `!is_leaf` condition is then false and the unconditional `CHECK` aborts the process. Startup is unaffected because `GrantAccess` only inserts when the path is not already covered, so the colliding insert is skipped while flags are applied. `RebuildTree` replays the recorded grant list unconditionally, so the first `permission.drop()` performs the insert that startup skipped. Inserting a path that is already granted is a no-op, so return the existing node instead of asserting. Fixes: nodejs#66901 Assisted-by: a closed-source coding agent
781e62d to
18c9de5
Compare
Fixes: #66901
permission.drop()aborts the process when two--allow-fs-readgrants are identical up to and including their first*.Reproduced on v26.12.0, v26.12.1 and a v27 nightly, on Windows and independently on macOS arm64. v26.11.1 and v24.22.1 are unaffected.
Cause
InsertcallsCreateChildat each*and at the final character. Inserting/tmp/sig/*.jsafter/tmp/sig/*reaches the node whose prefix is already/tmp/sig/*; the prefix-swap loop runs to completion because the strings match,child_was_end_nodesetsis_leaf = true, and it recurses asCreateChild(""). Thepath_prefix.empty() && !is_leafcondition is then false, so control reachesCHECK(!path_prefix.empty()), which is unconditional in release builds.That condition encodes an assumption that an empty remaining prefix cannot occur on a node that is already a leaf. It can, as soon as a grant list containing both paths is replayed.
Startup is unaffected because
GrantAccessonly inserts when!Lookup(path), and/tmp/sig/*.jsis already covered by/tmp/sig/*. #66556 moved thegranted_paths_push_backout of that branch so every explicit grant is recorded even when covered, andRebuildTreereplays the whole list, so the firstpermission.drop()performs the insert that startup skipped.Change
Inserting a path that is already granted is a no-op, so return the existing node rather than asserting.
Tests
Adds
test/parallel/test-permission-drop-colliding-wildcards.js: the colliding grants plus adrop()must exit 0 and not be killed by a signal, the single-grant case must keep working, and the colliding grants must still grant what they declare.How the change was verified
The C++ change was validated by extracting
RadixTreeverbatim frommain(fs_permission.h28-183 andfs_permission.cc268-332,diff-verified byte-identical, with onlykPathSeparator,CHECK, the debug plumbing andPrintTreestubbed) and driving it directly under-fsanitize=address,undefined. The same expectation table runs against an unpatched and a patched extract: 7 mismatches before, 0 after, no sanitizer reports.The table includes regression controls for both prior security fixes in this file, since a change to
CreateChildcould plausibly reopen either:/tmp/secret1,/tmp/secret2,/tmp/secret3inserted, the shared prefix/tmp/secretmust stay denied and the siblings granted./home/test*must not grant/home/tes;/t*must not grant/.doc/api/permissions.md:/home/test*grants/home/test2,/home/test/*grants the directory and its contents, a trailing-separator grant still matches.All pass with the change.