From 34eccd4d6f25e454e4b20db10a5bf62dd1bff602 Mon Sep 17 00:00:00 2001 From: uwezkhan Date: Tue, 9 Jun 2026 10:24:33 +0530 Subject: [PATCH] permission: fix fs allowlist bypass on shared path prefixes Inserting a path through an existing radix tree split node marked that intermediate node as an end node, so a shared prefix of several granted paths was treated as granted on its own. Mark a node as an end node only when the inserted path terminates exactly at it. --- src/permission/fs_permission.h | 10 ++++- .../test-permission-fs-read-shared-prefix.js | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-permission-fs-read-shared-prefix.js diff --git a/src/permission/fs_permission.h b/src/permission/fs_permission.h index 19d72ef654c9f0..7874c41ae23d1d 100644 --- a/src/permission/fs_permission.h +++ b/src/permission/fs_permission.h @@ -68,7 +68,15 @@ class FSPermission final : public PermissionBase { return split_child->CreateChild(path_prefix.substr(i)); } } - child->is_leaf = true; + if (i == path_prefix.length()) { + // The inserted path terminates exactly at this node, so it is a + // valid end node. When path_prefix extends past child->prefix this + // is only an intermediate node on the way to a deeper path and must + // not be marked as an end node, otherwise the shared prefix would be + // treated as granted on its own. + child->is_leaf = true; + return child; + } return child->CreateChild(path_prefix.substr(i)); } diff --git a/test/parallel/test-permission-fs-read-shared-prefix.js b/test/parallel/test-permission-fs-read-shared-prefix.js new file mode 100644 index 00000000000000..75f68d640dce6f --- /dev/null +++ b/test/parallel/test-permission-fs-read-shared-prefix.js @@ -0,0 +1,37 @@ +'use strict'; + +require('../common'); + +const { spawnSync } = require('child_process'); +const assert = require('assert'); +const path = require('path'); + +// Granting several paths that share a common prefix must not implicitly +// grant access to the shared prefix itself. Inserting the third sibling used +// to mark the shared split node as an end node in the permission radix tree. +{ + const prefix = path.resolve('./shared-prefix-dir/app'); + const f1 = `${prefix}1.log`; + const f2 = `${prefix}2.log`; + const f3 = `${prefix}3.log`; + const { status, stdout } = spawnSync( + process.execPath, + [ + '--permission', + `--allow-fs-read=${f1}`, + `--allow-fs-read=${f2}`, + `--allow-fs-read=${f3}`, + '-e', + `console.log(process.permission.has("fs.read", ${JSON.stringify(prefix)})); + console.log(process.permission.has("fs.read", ${JSON.stringify(f1)})); + console.log(process.permission.has("fs.read", ${JSON.stringify(f2)})); + console.log(process.permission.has("fs.read", ${JSON.stringify(f3)}));`, + ] + ); + const [sharedPrefix, allowed1, allowed2, allowed3] = stdout.toString().split('\n'); + assert.strictEqual(sharedPrefix, 'false'); + assert.strictEqual(allowed1, 'true'); + assert.strictEqual(allowed2, 'true'); + assert.strictEqual(allowed3, 'true'); + assert.strictEqual(status, 0); +}