diff --git a/src/Docker.jl b/src/Docker.jl index a4e1e7f..a79b0cd 100644 --- a/src/Docker.jl +++ b/src/Docker.jl @@ -141,6 +141,13 @@ function commit_previous_run(exe::DockerExecutor, image_name::String) return image_name end +function build_executor_command(exe::DockerExecutor, config::SandboxConfig, user_cmd::Base.CmdRedirect) + if user_cmd.stream_no > 2 + error("DockerExecutor does not support redirection of streams other than stdin, stdout, and stderr") + end + return Base.CmdRedirect(build_executor_command(exe, confi, user_cmd.cmd), user_cmd.handle, user_cmd.stream_no, user_cmd.readable) +end + function build_executor_command(exe::DockerExecutor, config::SandboxConfig, user_cmd::Cmd) # Build the docker image that corresponds to this rootfs image_name = build_docker_image(config.mounts, config.uid, config.gid; verbose=config.verbose) diff --git a/src/Sandbox.jl b/src/Sandbox.jl index c63c856..fbad623 100644 --- a/src/Sandbox.jl +++ b/src/Sandbox.jl @@ -104,40 +104,40 @@ function warn_priviledged(::PrivilegedUserNamespacesExecutor) end warn_priviledged(::SandboxExecutor) = nothing -for f in (:run, :success) - @eval begin - function $f(exe::SandboxExecutor, config::SandboxConfig, user_cmd::Cmd) - # This is for `stdin` because when precompiling, it is closed, - # which causes `run()` to throw an error. - open_or_devnull(io) = isopen(io) ? io : devnull - - # Because Julia 1.8+ closes IOBuffers like `stdout` and `stderr`, we create temporary - # IOBuffers that get copied over to the persistent `stdin`/`stdout` after the run is complete. - temp_stdout = isa(config.stdout, IOBuffer) ? IOBuffer() : config.stdout - temp_stderr = isa(config.stderr, IOBuffer) ? IOBuffer() : config.stderr - cmd = pipeline( - build_executor_command(exe, config, user_cmd); - stdin=open_or_devnull(config.stdin), - stdout=temp_stdout, - stderr=temp_stderr, - ) - if config.verbose - @info("Running sandboxed command", user_cmd.exec) - end - warn_priviledged(exe) - ret = $f(cmd) +function _run(run_or_success, exe::SandboxExecutor, config::SandboxConfig, user_cmd::Union{Cmd, Base.CmdRedirect}) + # This is for `stdin` because when precompiling, it is closed, + # which causes `run()` to throw an error. + open_or_devnull(io) = isopen(io) ? io : devnull + + # Because Julia 1.8+ closes IOBuffers like `stdout` and `stderr`, we create temporary + # IOBuffers that get copied over to the persistent `stdin`/`stdout` after the run is complete. + temp_stdout = isa(config.stdout, IOBuffer) ? IOBuffer() : config.stdout + temp_stderr = isa(config.stderr, IOBuffer) ? IOBuffer() : config.stderr + cmd = pipeline( + build_executor_command(exe, config, user_cmd); + stdin=open_or_devnull(config.stdin), + stdout=temp_stdout, + stderr=temp_stderr, + ) + if config.verbose + base_cmd = user_cmd + while isa(base_cmd, Base.CmdRedirect); base_cmd = base_cmd.cmd; end + @info("Running sandboxed command", base_cmd.exec) + end + warn_priviledged(exe) + ret = run_or_success(cmd) - # If we were using temporary IOBuffers, write the result out to `config.std{out,err}` - if isa(temp_stdout, IOBuffer) - write(config.stdout, take!(temp_stdout)) - end - if isa(temp_stderr, IOBuffer) - write(config.stderr, take!(temp_stderr)) - end - return ret - end + # If we were using temporary IOBuffers, write the result out to `config.std{out,err}` + if isa(temp_stdout, IOBuffer) + write(config.stdout, take!(temp_stdout)) + end + if isa(temp_stderr, IOBuffer) + write(config.stderr, take!(temp_stderr)) end + return ret end +run(exe::SandboxExecutor, config::SandboxConfig, user_cmd::Union{Cmd, Base.CmdRedirect}) = _run(run, exe, config, user_cmd) +success(exe::SandboxExecutor, config::SandboxConfig, user_cmd::Union{Cmd, Base.CmdRedirect}) = _run(success, exe, config, user_cmd) """ with_executor(f::Function, ::Type{<:SandboxExecutor} = preferred_executor(); kwargs...) diff --git a/src/UserNamespaces.jl b/src/UserNamespaces.jl index 7695a98..58ae987 100644 --- a/src/UserNamespaces.jl +++ b/src/UserNamespaces.jl @@ -135,6 +135,10 @@ function check_overlayfs_loaded(;verbose::Bool = false) return true end +function build_executor_command(exe::UserNamespacesExecutor, config::SandboxConfig, user_cmd::Base.CmdRedirect) + return Base.CmdRedirect(build_executor_command(exe, config, user_cmd.cmd), user_cmd.handle, user_cmd.stream_no, user_cmd.readable) +end + function build_executor_command(exe::UserNamespacesExecutor, config::SandboxConfig, user_cmd::Cmd) # While we would usually prefer to use the `executable_product()` function to get a # `Cmd` object that has all of the `PATH` and `LD_LIBRARY_PATH` environment variables diff --git a/test/UserNamespaces.jl b/test/UserNamespaces.jl index 4060e65..30bcf83 100644 --- a/test/UserNamespaces.jl +++ b/test/UserNamespaces.jl @@ -94,6 +94,15 @@ if executor_available(UnprivilegedUserNamespacesExecutor) @test String(take!(stdout)) == "received SIGINT\nreceived SIGTERM\n" end end + + @testset "Extra fds" begin + config = SandboxConfig(Dict("/" => Sandbox.debian_rootfs())) + with_executor(UnprivilegedUserNamespacesExecutor) do exe + buf = IOBuffer() + @test success(exe, config, Base.CmdRedirect(`/bin/sh -c "echo hello >&3"`, buf, 3)) + @test String(take!(buf)) == "hello\n" + end + end else @error("Skipping Unprivileged tests, as it does not seem to be available") end