)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"8167707d5e1459a84fc16783685a8acaa2af4737","unresolved":true,"context_lines":[{"line_number":7,"context_line":"fix(cppnix/libutil): ensure _deletePath doesn\u0027t use abs paths w/ dirfds"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"When calling `_deletePath` with a parent file descriptor, `openat` is"},{"line_number":10,"context_line":"made effective by using relative paths to the directory file descriptor."},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"To avoid the problem, the signature is changed to resist misuse with an"},{"line_number":13,"context_line":"assert in the prologue of the function."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"8a863141_96766662","line":10,"updated":"2026-01-17 03:18:02.000000000","message":"```suggestion\ncalled with a path relative to the directory file descriptor.\n\nThis causes a TOCTOU problem: another process can replace one of the directories between the parent dirfd and the path being deleted with a symbolic link.  `_deletePath` will then follow that link, allowing it to be fooled into deleting arbitrary files.\n```","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"},{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"8167707d5e1459a84fc16783685a8acaa2af4737","unresolved":true,"context_lines":[{"line_number":9,"context_line":"When calling `_deletePath` with a parent file descriptor, `openat` is"},{"line_number":10,"context_line":"made effective by using relative paths to the directory file descriptor."},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"To avoid the problem, the signature is changed to resist misuse with an"},{"line_number":13,"context_line":"assert in the prologue of the function."},{"line_number":14,"context_line":""},{"line_number":15,"context_line":"Fixes CVE-2025-46415."},{"line_number":16,"context_line":""},{"line_number":17,"context_line":"Change-Id: I6b3fc766bad2afe54dc27d47d1df3873e188de96"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"0bf2bd5f_2d674e54","line":14,"range":{"start_line":12,"start_character":0,"end_line":14,"end_character":1},"updated":"2026-01-17 03:18:02.000000000","message":"```suggestion\nTo avoid the problem, the signature is changed to resist misuse by taking a *name* rather than a Path, and asserting that the name does not contain `/` in the prologue of the function.\n```","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"8167707d5e1459a84fc16783685a8acaa2af4737","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"119af031_f5cf12be","updated":"2026-01-17 03:18:02.000000000","message":"If you want to avoid diverging from the Lix patch this was cherry-picked from it\u0027s okay to disregard my change suggestions.","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"}],"third_party/cppnix/src/libutil/util.cc":[{"author":{"_account_id":1000034,"name":"sterni","email":"sternenseemann@systemli.org","username":"sterni"},"change_message_id":"5a529930ab3a4e8474921a3a85344758c560024d","unresolved":true,"context_lines":[{"line_number":436,"context_line":"}"},{"line_number":437,"context_line":""},{"line_number":438,"context_line":""},{"line_number":439,"context_line":"static void _deletePath(int parentfd, const Path\u0026 name, unsigned long long \u0026 bytesFreed)"},{"line_number":440,"context_line":"{"},{"line_number":441,"context_line":"    /* This ensures that `name` is an immediate child of `parentfd`. */"},{"line_number":442,"context_line":"    assert(!name.empty() \u0026\u0026 name.find(\u0027/\u0027) \u003d\u003d std::string::npos \u0026\u0026 \"`name` is an immediate child to `parentfd`\");"}],"source_content_type":"text/x-c","patch_set":1,"id":"b6ea952c_3a9978ee","line":439,"updated":"2025-11-16 14:16:40.000000000","message":"Why are we continuing to use Path here? (https://github.com/tvlfyi/nix/pull/5/commits/3f3c4e8fd77117050c8b2c695704e98c1825591f#r2174764700)","commit_id":"6e87c5f9c136bdd82257ae1403fcb486108e4e95"},{"author":{"_account_id":1000034,"name":"sterni","email":"sternenseemann@systemli.org","username":"sterni"},"change_message_id":"bfce6fe31933837c919c40bea0bec843f35c960d","unresolved":true,"context_lines":[{"line_number":436,"context_line":"}"},{"line_number":437,"context_line":""},{"line_number":438,"context_line":""},{"line_number":439,"context_line":"static void _deletePath(int parentfd, const Path\u0026 name, unsigned long long \u0026 bytesFreed)"},{"line_number":440,"context_line":"{"},{"line_number":441,"context_line":"    /* This ensures that `name` is an immediate child of `parentfd`. */"},{"line_number":442,"context_line":"    assert(!name.empty() \u0026\u0026 name.find(\u0027/\u0027) \u003d\u003d std::string::npos \u0026\u0026 \"`name` is an immediate child to `parentfd`\");"}],"source_content_type":"text/x-c","patch_set":1,"id":"471a374e_350b52e2","line":439,"in_reply_to":"b6ea952c_3a9978ee","updated":"2025-11-16 18:08:02.000000000","message":"I\u0027ve changed this to match https://git.lix.systems/lix-project/lix/commit/11c5e3bbcc38ab843ff5c8a9d3025923ad6bf4e3 more closely now.","commit_id":"6e87c5f9c136bdd82257ae1403fcb486108e4e95"},{"author":{"_account_id":1000034,"name":"sterni","email":"sternenseemann@systemli.org","username":"sterni"},"change_message_id":"bfce6fe31933837c919c40bea0bec843f35c960d","unresolved":true,"context_lines":[{"line_number":500,"context_line":"        throw SysError(format(\"opening directory \u0027%1%\u0027\") % path);"},{"line_number":501,"context_line":"    }"},{"line_number":502,"context_line":""},{"line_number":503,"context_line":"    std::string name \u003d baseNameOf(path);"},{"line_number":504,"context_line":"    _deletePath(dirfd.get(), name, bytesFreed);"},{"line_number":505,"context_line":"}"},{"line_number":506,"context_line":""}],"source_content_type":"text/x-c","patch_set":2,"id":"af18c9c0_28c090c6","line":503,"updated":"2025-11-16 18:08:02.000000000","message":"Note that we diverge from https://git.lix.systems/lix-project/lix/commit/11c5e3bbcc38ab843ff5c8a9d3025923ad6bf4e3 here, since `baseNameOf` returns `std::string` for us, but `std::string_view` for later versions of Nix.","commit_id":"5c2c7f6e160e932b283001d786e04edaabf65d1c"},{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"8167707d5e1459a84fc16783685a8acaa2af4737","unresolved":true,"context_lines":[{"line_number":436,"context_line":"}"},{"line_number":437,"context_line":""},{"line_number":438,"context_line":""},{"line_number":439,"context_line":"/* TODO: a better structure that links all parent fds for the traveral root"},{"line_number":440,"context_line":" * should be considered for this code"},{"line_number":441,"context_line":" */"},{"line_number":442,"context_line":"static void _deletePath(int parentfd, const std::string \u0026 name, unsigned long long \u0026 bytesFreed)"},{"line_number":443,"context_line":"{"},{"line_number":444,"context_line":"    /* This ensures that `name` is an immediate child of `parentfd`. */"}],"source_content_type":"text/x-c","patch_set":7,"id":"1aef9f95_68837cd8","line":441,"range":{"start_line":439,"start_character":0,"end_line":441,"end_character":3},"updated":"2026-01-17 03:18:02.000000000","message":"I don\u0027t understand this comment.  You mean passing a `vec` of all the dirfd\u0027s of all the directories between the root of the deletion operation and the subpath being deleted in this call?  What benefit would that provide?","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"},{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"0c77278d5b54fa4564addcb35d872568fa0cc179","unresolved":true,"context_lines":[{"line_number":445,"context_line":"    assert(!name.empty() \u0026\u0026 name.find(\u0027/\u0027) \u003d\u003d std::string::npos \u0026\u0026 \"`name` is an immediate child to `parentfd`\");"},{"line_number":446,"context_line":"    checkInterrupt();"},{"line_number":447,"context_line":""},{"line_number":448,"context_line":"    /* FIXME: there\u0027s a minor TOCTOU here."},{"line_number":449,"context_line":"     * we fstatat the inode nofollow, check if this is a directory"},{"line_number":450,"context_line":"     * and then open it."},{"line_number":451,"context_line":"     * a better alternative is open it as O_PATH as a namefd."},{"line_number":452,"context_line":"     * if it\u0027s a directory, it can be openat with the namefd."},{"line_number":453,"context_line":"     */"}],"source_content_type":"text/x-c","patch_set":7,"id":"7099852b_9e2719f9","line":450,"range":{"start_line":448,"start_character":0,"end_line":450,"end_character":24},"updated":"2026-01-17 07:42:47.000000000","message":"Yeah but it\u0027s not exploitable.\n\nIf it was a directory when `fstatat()` was called, and the attacker replaces it with a symlink immediately afterwards, the `openat(,,O_NOFOLLOW)` will fail.\n\nIf it wasn\u0027t a directory when `fstatat()` was called, and the attacker replaces it with a directory immediately afterwards, the `unlinkat()` will fail.\n\nI don\u0027t see an attack here.","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"},{"author":{"_account_id":1000066,"name":"Adam Joseph","display_name":"amjoseph","email":"adam@westernsemico.com","username":"amjoseph"},"change_message_id":"8167707d5e1459a84fc16783685a8acaa2af4737","unresolved":true,"context_lines":[{"line_number":448,"context_line":"    /* FIXME: there\u0027s a minor TOCTOU here."},{"line_number":449,"context_line":"     * we fstatat the inode nofollow, check if this is a directory"},{"line_number":450,"context_line":"     * and then open it."},{"line_number":451,"context_line":"     * a better alternative is open it as O_PATH as a namefd."},{"line_number":452,"context_line":"     * if it\u0027s a directory, it can be openat with the namefd."},{"line_number":453,"context_line":"     */"},{"line_number":454,"context_line":""},{"line_number":455,"context_line":"    struct stat st;"}],"source_content_type":"text/x-c","patch_set":7,"id":"d3d7324d_1daf8d60","line":452,"range":{"start_line":451,"start_character":4,"end_line":452,"end_character":61},"updated":"2026-01-17 03:18:02.000000000","message":"I don\u0027t think this fixes the TOCTOU; a \"namefd\" in userspace just references a dirfd-and-a-`char*` in kernelspace.  I don\u0027t think the namefd becomes somehow invalid if another process modifies the directory entry.\n\nAFAICT `O_PATH` mainly exists to accomodate situations where you have `x` permission but don\u0027t have `r` permission.\n\nI think the way to truly fix the TOCTOU is to blindly attempt an `unlinkat(,,0)`.  If that fails with `EISDIR` then assume the entry is a directory.  This pushes the TOCTOU inside the kernel, where the \"is this a directory, if it isn\u0027t unlink it\" can be done atomically.","commit_id":"ebd034024cfde106895bbece9b5ab21bd89a7e88"}]}
