// Copyright (C) 2026 Viorel Munteanu // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #pragma once #include #include #include #include "volume_mount.h" // Probes the running kernel for which Linux namespace types can actually be // unshared and returns the corresponding bwrap --unshare-xxx flags for the // ones that are supported. Intended for kernels with partial namespace // support (e.g. stock Android kernels), where blindly passing every // --unshare-xxx flag to bwrap would make it fail outright. std::vector detect_bwrap_unshare_args(); // Wraps `argv` so it runs inside the mount+user namespace that containers-storage's // rootless fuse-overlayfs daemon created for `root` (the merged mount path from // mount_layer()), via nsenter -- needed to reach anything under `root` at all from a // plain process outside that namespace (containers-storage mount reexecs itself // into a private namespace to get the privilege an unprivileged overlay mount // needs; only nsenter targeting that live daemon's PID can rejoin it). If // use_nsenter is false, returns argv unchanged (the mount is already directly // visible, e.g. when running as root -- see run_container() in main.cpp). Returns // nullopt (and logs) if the fuse-overlayfs process or nsenter itself can't be // found. std::optional> wrap_for_root_namespace(const std::string& root, bool use_nsenter, const std::vector& argv); struct ResolvedUser { int uid; int gid; }; // Assembles the full bwrap argv (program name included) to run `command` with // `root` bound as the sandbox's filesystem root, using whichever --unshare-xxx // flags the kernel supports (see detect_bwrap_unshare_args()). Each entry in // `volumes` is bound writably at its container_path (see resolve_volume_mount() // in volume_mount.h). If `user` is set, the command is wrapped so it drops to that // uid/gid before running -- see run_bwrap() for how, since bwrap's own --uid/--gid // require --unshare-user, which isn't requested when running as root (see // detect_bwrap_unshare_args()). std::vector build_bwrap_args(const std::string& root, const std::vector& command, const std::vector& volumes, std::optional user); // Runs bwrap against `root` (the merged mount path from mount_layer()) in the // foreground and waits for it to exit. If `use_nsenter` is true, first locates the // fuse-overlayfs process serving `root` and runs bwrap via nsenter into that // process's user+mount namespaces -- needed because containers-storage mount // (rootless) creates the overlay mount inside a private namespace invisible to a // plain child process on kernels where fuse-overlayfs isolates it that way. Pass // use_nsenter=false on kernels where the mount is already directly visible // (observed on kernels older than 4.18, per fuse-overlayfs's own release notes). // If `user` is set, this process's own binary is bind-mounted into the sandbox and // used to drop privileges to that uid/gid before running `command` -- see // build_bwrap_args(). Returns bwrap's exit code, or -1 on failure to launch. int run_bwrap(const std::string& root, const std::vector& command, bool use_nsenter, const std::vector& volumes, std::optional user);