sys-devel/llvm offers the C++ function llvm::sys::fs::create_directories. As opposed to something like mkdir, the function handles cases such as `~/nonexistent/../directory` by creating `~/nonexistent` and `~/directory`. When ran inside the sandbox, however, the previous operation fails. A C++ program to demonstrate the behavior: ------ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include <iostream> using namespace llvm; using namespace llvm::sys::fs; using namespace llvm::sys::path; int main (int argc, char **argv) { if (argc < 2) { return 1; } SmallString<128> path; append(path, argv[1]); std::clog << "Trying to create: " << path.c_str() << '\n'; const auto ec = create_directories(path); std::clog << "Error code: " << ec << '\n'; return ec.value(); } ------ Without sandbox: ------ $ strace -e file ./main ~/nonexistent/../directory execve("./main", ["./main", "/home/happy/nonexistent/../direc"...], 0x7ffd4d8674b8 /* 75 vars */) = 0 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=133875, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/libLLVM-16.so", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=132698120, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/gcc/x86_64-pc-linux-gnu/14/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2685424, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=907376, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/gcc/x86_64-pc-linux-gnu/14/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=186296, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=1929696, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v3/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v3", 0x7ffd2a1f8ff0, 0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v2/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v2", 0x7ffd2a1f8ff0, 0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0 openat(AT_FDCWD, "/usr/lib64/libffi.so.8", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=47104, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=104384, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libzstd.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libzstd.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=739184, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libtinfo.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libtinfo.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=261192, ...}, AT_EMPTY_PATH) = 0 newfstatat(2, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0xb), ...}, AT_EMPTY_PATH) = 0 Trying to create: /home/happy/nonexistent/../directory mkdir("/home/happy/nonexistent/../directory", 0770) = -1 ENOENT (No such file or directory) mkdir("/home/happy/nonexistent/..", 0770) = -1 ENOENT (No such file or directory) mkdir("/home/happy/nonexistent", 0770) = 0 mkdir("/home/happy/nonexistent/..", 0770) = -1 EEXIST (File exists) mkdir("/home/happy/nonexistent/../directory", 0770) = 0 Error code: system:0 +++ exited with 0 +++ ------ With sandbox: ------ $ LD_PRELOAD=/usr/lib64/libsandbox.so strace -e file ./main ~/nonexistent/../directory execve("./main", ["./main", "/home/happy/nonexistent/../direc"...], 0x7fff25401ba8 /* 76 vars */) = 0 openat(AT_FDCWD, "/usr/lib64/libsandbox.so", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=96248, ...}, AT_EMPTY_PATH) = 0 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=133875, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/libLLVM-16.so", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=132698120, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/gcc/x86_64-pc-linux-gnu/14/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2685424, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/lib64/libm.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=907376, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/gcc/x86_64-pc-linux-gnu/14/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=186296, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=1929696, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v3/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v3", 0x7ffe1a5699c0, 0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v2/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/glibc-hwcaps/x86-64-v2", 0x7ffe1a5699c0, 0) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libffi.so.8", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0 openat(AT_FDCWD, "/usr/lib64/libffi.so.8", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=47104, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libz.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=104384, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libzstd.so.1", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libzstd.so.1", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=739184, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "/usr/lib/llvm/16/lib64/../lib64/libtinfo.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib64/libtinfo.so.6", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=261192, ...}, AT_EMPTY_PATH) = 0 newfstatat(2, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0xb), ...}, AT_EMPTY_PATH) = 0 Trying to create: /home/happy/nonexistent/../directory newfstatat(AT_FDCWD, "/home/happy/directory", 0x7ffe1a5664c0, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory) mkdir("/home/happy/nonexistent/../directory", 0770) = -1 ENOENT (No such file or directory) newfstatat(AT_FDCWD, "/home/happy", {st_mode=S_IFDIR|0755, st_size=20480, ...}, AT_SYMLINK_NOFOLLOW) = 0 newfstatat(AT_FDCWD, "/home/happy/directory", 0x7ffe1a5664c0, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory) mkdir("/home/happy/nonexistent/../directory", 0770) = -1 ENOENT (No such file or directory) Error code: generic:2 +++ exited with 2 +++ -------
mkdir -p is a better comparison to create_directories with the exception that it works inside the sandbox.
It's not clear to me how the sandbox code is interfering at all here. You will need to debug this a bit more to identify the problem.
I'm all for helping and debugging some more, it's just that the sandbox is pretty low level and I don't know anything about all of this. All I know is that 1 function works outside the sandbox while inside the sandbox it fails.
(In reply to Horodniceanu Andrei from comment #3) Someone needs to debug the llvm create_directories function to determine where the behavior differs exactly.
My best guess is that sandbox is returning ENOENT or EEXIST in a context where the LLVM code expects to see the opposite.
Ah, I think I may see what is happening. I think problem is the difference in behavior between these commands: % strace -e mkdir mkdir /tmp/foo/.. mkdir("/tmp/foo/..", 0777) = -1 ENOENT (No such file or directory) mkdir: cannot create directory ‘/tmp/foo/..’: No such file or directory +++ exited with 1 +++ % sandbox strace -e mkdir mkdir /tmp/foo/.. mkdir: cannot create directory ‘/tmp/foo/..’: File exists +++ exited with 1 +++ Without sandbox, /tmp/foo/ does not exist, so we get ENOENT from the kernel. With sandbox, /tmp/foo/.. gets canonicalized to /tmp, and sandbox returns EEXIST.
Please give the PR a try.
(In reply to Mike Gilbert from comment #7) > Please give the PR a try. Fixed the issue. Thanks Mike
The bug has been referenced in the following commit(s): https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=ef9208bea4e0f0dff5abf358002565f36e4d7a8d commit ef9208bea4e0f0dff5abf358002565f36e4d7a8d Author: Mike Gilbert <floppym@gentoo.org> AuthorDate: 2024-01-08 19:59:35 +0000 Commit: Mike Gilbert <floppym@gentoo.org> CommitDate: 2024-01-08 20:04:09 +0000 libsandbox: stat the original path for EEXIST hackaround Resolves an issue that can occur with paths that contain parent directory references (/../). If part of the path does not exist, the sandboxed program should get ENOENT, not EEXIST. If we use the canonicalized path, intermediate paths will be eliminated and we produce the wrong result. Bug: https://bugs.gentoo.org/921581 Signed-off-by: Mike Gilbert <floppym@gentoo.org> libsandbox/pre_check_mkdirat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Hello everyone, As you know, sandboxed mkdirat fails with EEXIST while ENOENT is expected. Due to this bug, the 'fs.test.test.makepath relative walks' test of dev-lang/zig-0.13.0 (not in the main tree yet, see https://github.com/gentoo/gentoo/pull/37257) fails in case it tries to create a directory using the libc (there are other implementations that are not affected). I submitted a patch for Zig (https://github.com/ziglang/zig/pull/20397), but now I've figured out that the root cause is this sandbox issue. I see that Mike Gilbert already fixed it in the sandbox repository, but that fix itself causes sandbox violations where there were no violations since EEXIST was expected instead. So I'm going to submit a PR with an alternative fix.
There is a mistake in the last paragraph of my previous message: there must be ENOENT, not EEXIST.
The bug has been referenced in the following commit(s): https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=de4f57761821e3d97e841a99af38768ee9605633 commit de4f57761821e3d97e841a99af38768ee9605633 Author: Aliaksei Urbanski <aliaksei.urbanski@gmail.com> AuthorDate: 2024-06-27 03:51:47 +0000 Commit: Mike Gilbert <floppym@gentoo.org> CommitDate: 2024-06-27 15:23:48 +0000 libsandbox: fix violations where ENOENT is expected These changes revert f7d02c04 that aimed to resolve 921581 and fix it in a way that doesn't cause unwanted sandbox violations. Bug: https://bugs.gentoo.org/921581 Signed-off-by: Aliaksei Urbanski <aliaksei.urbanski@gmail.com> Signed-off-by: Mike Gilbert <floppym@gentoo.org> libsandbox/pre_check_mkdirat.c | 8 +++++--- tests/mkdirat-3.sh | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-)
The bug has been closed via the following commit(s): https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=23af26a6cc7fa2537e7f7d5ea4a65c7ad68c5eee commit 23af26a6cc7fa2537e7f7d5ea4a65c7ad68c5eee Author: Mike Gilbert <floppym@gentoo.org> AuthorDate: 2024-06-27 18:38:15 +0000 Commit: Mike Gilbert <floppym@gentoo.org> CommitDate: 2024-06-27 18:39:32 +0000 sys-apps/sandbox: add 2.39 Closes: https://bugs.gentoo.org/923013 Closes: https://bugs.gentoo.org/921581 Signed-off-by: Mike Gilbert <floppym@gentoo.org> sys-apps/sandbox/Manifest | 1 + sys-apps/sandbox/sandbox-2.39.ebuild | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+)
The bug has been referenced in the following commit(s): https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=4b40e4489a7793d888ae55ecbb3ca560889a5a14 commit 4b40e4489a7793d888ae55ecbb3ca560889a5a14 Author: Aliaksei Urbanski <aliaksei.urbanski@gmail.com> AuthorDate: 2024-06-27 03:51:47 +0000 Commit: Mike Gilbert <floppym@gentoo.org> CommitDate: 2024-06-27 15:24:50 +0000 libsandbox: fix violations where ENOENT is expected These changes revert f7d02c04 that aimed to resolve 921581 and fix it in a way that doesn't cause unwanted sandbox violations. Bug: https://bugs.gentoo.org/921581 Signed-off-by: Aliaksei Urbanski <aliaksei.urbanski@gmail.com> Signed-off-by: Mike Gilbert <floppym@gentoo.org> (cherry picked from commit de4f57761821e3d97e841a99af38768ee9605633) libsandbox/pre_check_mkdirat.c | 8 +++++--- tests/mkdirat-3.sh | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) https://gitweb.gentoo.org/proj/sandbox.git/commit/?id=f7d02c04b2a8e395f478bda03306fb68fb44ba4c commit f7d02c04b2a8e395f478bda03306fb68fb44ba4c Author: Mike Gilbert <floppym@gentoo.org> AuthorDate: 2024-01-08 19:59:35 +0000 Commit: Mike Gilbert <floppym@gentoo.org> CommitDate: 2024-01-22 21:41:13 +0000 libsandbox: stat the original path for EEXIST hackaround Resolves an issue that can occur with paths that contain parent directory references (/../). If part of the path does not exist, the sandboxed program should get ENOENT, not EEXIST. If we use the canonicalized path, intermediate paths will be eliminated and we produce the wrong result. Bug: https://bugs.gentoo.org/921581 Signed-off-by: Mike Gilbert <floppym@gentoo.org> (cherry picked from commit ef9208bea4e0f0dff5abf358002565f36e4d7a8d) libsandbox/pre_check_mkdirat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)