diff --git a/CLAUDE.md b/CLAUDE.md index 7ed6a15..4603a28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -115,11 +115,17 @@ Source layout (all under `src/`): a name or numeric id) against the *mounted image's own* `/etc/passwd`/`/etc/group` (not the host's), since names like `git` only mean anything inside that image's own user database. A numeric user with no group and no matching `/etc/passwd` entry - defaults gid to the same numeric value as the uid. `run_container()` (`main.cpp`) - calls this with either the explicit `--user`/`--group` flags, or, when `--user` - wasn't given, the image's own declared `config.User` (`OciImageConfig::user`/ - `group`) — so a container defaults to running as whatever user the image itself - declares, not root, unless the image declares none. + defaults gid to the same numeric value as the uid. `ResolvedUser` (`bwrap.h`) also + carries `home`, looked up by the final resolved uid's `/etc/passwd` entry (field 5) + regardless of whether `user` was given as a name or a number; falls back to + `"/root"` for uid 0 or `"/"` otherwise when there's no matching row. + `build_bwrap_args()` (`bwrap.cpp`) sets the sandboxed process's `HOME` from this — + `"/root"` only when no user override applies at all (no `--user`, no image-declared + `config.User`). `run_container()` (`main.cpp`) calls `resolve_user_and_group()` + with either the explicit `--user`/`--group` flags, or, when `--user` wasn't given, + the image's own declared `config.User` (`OciImageConfig::user`/`group`) — so a + container defaults to running as whatever user the image itself declares, not + root, unless the image declares none. - `process.{h,cpp}` — argv-based subprocess helpers (fork/execvp, no shell): `run_process()` captures stdout (used for `containers-storage` calls), `run_process_foreground()` inherits all of stdio (used for the interactive `bwrap` diff --git a/src/bwrap.cpp b/src/bwrap.cpp index 6829084..7d4c81f 100644 --- a/src/bwrap.cpp +++ b/src/bwrap.cpp @@ -227,11 +227,9 @@ std::vector build_bwrap_args(const std::string& root, "--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "--setenv", - "HOME", - "/root", }; args.insert(args.end(), filesystem_args.begin(), filesystem_args.end()); + args.insert(args.end(), {"--setenv", "HOME", user ? user->home : "/root"}); for (const auto& volume : volumes) { args.insert(args.end(), {"--bind", volume.host_directory, volume.container_path}); diff --git a/src/bwrap.h b/src/bwrap.h index 844f021..3796d53 100644 --- a/src/bwrap.h +++ b/src/bwrap.h @@ -45,6 +45,7 @@ std::optional> wrap_for_root_namespace(const std::strin struct ResolvedUser { int uid; int gid; + std::string home; // from the image's /etc/passwd entry for uid, or a sane fallback }; // Assembles the full bwrap argv (program name included) to run `command` with diff --git a/src/user_spec.cpp b/src/user_spec.cpp index a5616cd..7328488 100644 --- a/src/user_spec.cpp +++ b/src/user_spec.cpp @@ -119,5 +119,15 @@ std::optional resolve_user_and_group(const std::string& user, } } - return ResolvedUser{uid, gid}; + // Looked up by the final resolved uid (field 2), independent of whether `user` + // was given as a name or a number, so it matches whichever /etc/passwd row + // actually owns that uid. No matching row -> fall back to "/root" for uid 0 + // (matches useradd-less images' own convention for root) or "/" otherwise. + std::string home = uid == 0 ? "/root" : "/"; + auto home_entry = lookup_entry(passwd_file, std::to_string(uid), 2); + if (home_entry && home_entry->size() > 5 && !(*home_entry)[5].empty()) { + home = (*home_entry)[5]; + } + + return ResolvedUser{uid, gid, home}; }