Add --hostname to set the sandbox's hostname under -r/--run

Long-option only. Threaded through run_container() into
build_bwrap_args(), which passes it as bwrap's own --hostname only
when --unshare-uts is actually among the flags being given to bwrap
(bwrap itself refuses --hostname without it) -- otherwise logs a
warning and leaves the hostname alone, since a stock Android kernel
in degraded mode may not support a UTS namespace at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-22 07:13:22 +00:00
parent 355675b43f
commit 9877ffe7f1
5 changed files with 65 additions and 17 deletions
+20 -3
View File
@@ -179,7 +179,8 @@ std::vector<std::string> detect_bwrap_unshare_args() {
std::vector<std::string> build_bwrap_args(const std::string& root,
const std::vector<std::string>& command,
const std::vector<ResolvedVolumeMount>& volumes,
std::optional<ResolvedUser> user) {
std::optional<ResolvedUser> user,
const std::optional<std::string>& hostname) {
// --new-session detaches from the controlling terminal, which breaks job
// control for an interactive foreground shell ("can't access tty"). Re-enable
// once background/daemonized runs are implemented, where that's the point.
@@ -187,6 +188,7 @@ std::vector<std::string> build_bwrap_args(const std::string& root,
auto unshare_args = detect_bwrap_unshare_args();
bool has_pid_ns = false;
bool has_uts_ns = false;
for (const auto& arg : unshare_args) {
// Not requested yet: without any network setup (slirp4netns or similar),
// unsharing it just leaves the sandbox with no network at all. Re-add once
@@ -199,12 +201,26 @@ std::vector<std::string> build_bwrap_args(const std::string& root,
args.push_back(arg);
if (arg == "--unshare-pid") {
has_pid_ns = true;
} else if (arg == "--unshare-uts") {
has_uts_ns = true;
}
}
if (has_pid_ns) {
args.push_back("--as-pid-1");
}
if (hostname) {
// bwrap itself refuses --hostname without --unshare-uts, so this must be
// guarded the same way rather than always passed through -- the kernel may
// not support a UTS namespace at all (degraded-mode Android kernels).
if (has_uts_ns) {
args.insert(args.end(), {"--hostname", *hostname});
} else {
spdlog::warn("--hostname requires an unshared UTS namespace, which the running "
"kernel doesn't support here; ignoring --hostname");
}
}
std::vector<std::string> filesystem_args = {
"--bind",
root,
@@ -288,14 +304,15 @@ std::optional<std::vector<std::string>> wrap_for_root_namespace(const std::strin
}
int run_bwrap(const std::string& root, const std::vector<std::string>& command, bool use_nsenter,
const std::vector<ResolvedVolumeMount>& volumes, std::optional<ResolvedUser> user) {
const std::vector<ResolvedVolumeMount>& volumes, std::optional<ResolvedUser> user,
const std::optional<std::string>& hostname) {
if (user && !find_priv_drop_helper()) {
spdlog::error("could not find the {} helper next to this binary; --user/--group requires it",
kPrivDropHelperName);
return -1;
}
auto bwrap_args = build_bwrap_args(root, command, volumes, user);
auto bwrap_args = build_bwrap_args(root, command, volumes, user, hostname);
auto argv = wrap_for_root_namespace(root, use_nsenter, bwrap_args);
if (!argv) {
return -1;
+10 -4
View File
@@ -55,11 +55,14 @@ struct ResolvedUser {
// 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()).
// detect_bwrap_unshare_args()). If `hostname` is set and the kernel supports
// --unshare-uts (bwrap refuses --hostname without it), passes it as bwrap's own
// --hostname; otherwise logs a warning and leaves the sandbox's hostname alone.
std::vector<std::string> build_bwrap_args(const std::string& root,
const std::vector<std::string>& command,
const std::vector<ResolvedVolumeMount>& volumes,
std::optional<ResolvedUser> user);
std::optional<ResolvedUser> user,
const std::optional<std::string>& hostname);
// 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
@@ -71,6 +74,9 @@ std::vector<std::string> build_bwrap_args(const std::string& root,
// (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.
// build_bwrap_args(). `hostname`, if set, is forwarded to build_bwrap_args() --
// see there for when it does/doesn't take effect. Returns bwrap's exit code, or -1
// on failure to launch.
int run_bwrap(const std::string& root, const std::vector<std::string>& command, bool use_nsenter,
const std::vector<ResolvedVolumeMount>& volumes, std::optional<ResolvedUser> user);
const std::vector<ResolvedVolumeMount>& volumes, std::optional<ResolvedUser> user,
const std::optional<std::string>& hostname);
+19 -7
View File
@@ -58,18 +58,19 @@ enum class Mode {
kInspect
};
// --log-level/--user/--group/--list-volumes/--delete-volume[-full] have no short
// form (--log-level's was freed up so -l could become --list-images; -u is already
// --umount; the rest have no natural free letter left, or don't need one), so they
// need long-option vals outside the printable-char range short options use.
// --log-level/--user/--group/--list-volumes/--delete-volume[-full]/--hostname have
// no short form (--log-level's was freed up so -l could become --list-images; -u is
// already --umount; the rest have no natural free letter left, or don't need one),
// so they need long-option vals outside the printable-char range short options use.
constexpr int kLogLevelOpt = 256;
constexpr int kUserOpt = 257;
constexpr int kGroupOpt = 258;
constexpr int kListVolumesOpt = 259;
constexpr int kDeleteVolumeOpt = 260;
constexpr int kDeleteVolumeFullOpt = 261;
constexpr int kHostnameOpt = 262;
constexpr std::array<struct option, 18> kLongOptions = {{
constexpr std::array<struct option, 19> kLongOptions = {{
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'V'},
{"test", no_argument, nullptr, 't'},
@@ -87,6 +88,7 @@ constexpr std::array<struct option, 18> kLongOptions = {{
{"delete-volume", required_argument, nullptr, kDeleteVolumeOpt},
{"delete-volume-full", required_argument, nullptr, kDeleteVolumeFullOpt},
{"inspect", required_argument, nullptr, 'i'},
{"hostname", required_argument, nullptr, kHostnameOpt},
{nullptr, 0, nullptr, 0},
}};
@@ -131,6 +133,10 @@ void print_usage(const char* prog) {
" (no user namespace involved)\n"
" --group <group> with --user, use this group (name or numeric\n"
" gid) instead of the user's own primary group\n"
" --hostname <name> with --run, set the sandbox's hostname (only\n"
" takes effect if the running kernel supports\n"
" --unshare-uts; otherwise ignored with a\n"
" warning)\n"
" -l, --list-images <dir> list OCI Image Layout tars (*.tar, *.tar.*) found\n"
" directly in <dir>, with their name:tag\n"
" -i, --inspect <image.tar> print an image's declared user, exposed ports,\n"
@@ -453,6 +459,7 @@ int delete_volume_command(const std::string& name, const std::filesystem::path&
int run_container(const std::filesystem::path& image_tar,
const std::vector<std::string>& requested_command, bool use_nsenter,
const std::optional<std::string>& user, const std::optional<std::string>& group,
const std::optional<std::string>& hostname,
const std::vector<std::pair<std::string, std::string>>& volume_specs,
const AppConfig& app_config) {
auto mounted = mount_image(image_tar);
@@ -517,7 +524,7 @@ int run_container(const std::filesystem::path& image_tar,
int exit_code = -1;
if (ok) {
exit_code = run_bwrap(mounted->merged_path, command, use_nsenter, volume_mounts, resolved_user);
exit_code = run_bwrap(mounted->merged_path, command, use_nsenter, volume_mounts, resolved_user, hostname);
if (exit_code < 0) {
spdlog::error("failed to run bwrap");
}
@@ -550,6 +557,7 @@ int main(int argc, char* argv[]) {
bool disable_nsenter = false;
std::optional<std::string> user_flag;
std::optional<std::string> group_flag;
std::optional<std::string> hostname_flag;
std::vector<std::pair<std::string, std::string>> volume_specs;
opterr = 0;
@@ -645,6 +653,9 @@ int main(int argc, char* argv[]) {
case kGroupOpt:
group_flag = optarg;
break;
case kHostnameOpt:
hostname_flag = optarg;
break;
case ':':
spdlog::error("option requires an argument: -{}", static_cast<char>(optopt));
print_usage(argv[0]);
@@ -720,7 +731,8 @@ int main(int argc, char* argv[]) {
if (geteuid() == 0 && !disable_nsenter) {
spdlog::debug("running as root; skipping nsenter (the mount is already directly visible)");
}
return run_container(mode_arg, command, use_nsenter, user_flag, group_flag, volume_specs, *config);
return run_container(mode_arg, command, use_nsenter, user_flag, group_flag, hostname_flag, volume_specs,
*config);
}
auto mounted = mount_image(mode_arg);