From b113597a65bbd2b4b0b6b39b70131073cdd7302b Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Fri, 30 Jan 2026 12:15:26 -0800 Subject: [PATCH 1/7] feat(linux-sandbox): add build-time bubblewrap FFI path --- codex-rs/Cargo.lock | 2 + codex-rs/linux-sandbox/Cargo.toml | 4 + codex-rs/linux-sandbox/build.rs | 112 +++++++ .../scripts/test_linux_sandbox.sh | 191 ++++++++++++ codex-rs/linux-sandbox/src/bwrap.rs | 291 ++++++++++++++++++ codex-rs/linux-sandbox/src/lib.rs | 9 +- codex-rs/linux-sandbox/src/linux_run_main.rs | 205 +++++++++++- codex-rs/linux-sandbox/src/vendored_bwrap.rs | 50 +++ 8 files changed, 858 insertions(+), 6 deletions(-) create mode 100644 codex-rs/linux-sandbox/build.rs create mode 100644 codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh create mode 100644 codex-rs/linux-sandbox/src/bwrap.rs create mode 100644 codex-rs/linux-sandbox/src/vendored_bwrap.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 57186f82f1d..abf00bdbacc 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1657,11 +1657,13 @@ dependencies = [ name = "codex-linux-sandbox" version = "0.0.0" dependencies = [ + "cc", "clap", "codex-core", "codex-utils-absolute-path", "landlock", "libc", + "pkg-config", "pretty_assertions", "seccompiler", "tempfile", diff --git a/codex-rs/linux-sandbox/Cargo.toml b/codex-rs/linux-sandbox/Cargo.toml index 2b63d6b30f7..503c22e6fe2 100644 --- a/codex-rs/linux-sandbox/Cargo.toml +++ b/codex-rs/linux-sandbox/Cargo.toml @@ -33,3 +33,7 @@ tokio = { workspace = true, features = [ "rt-multi-thread", "signal", ] } + +[build-dependencies] +cc = "1" +pkg-config = "0.3" diff --git a/codex-rs/linux-sandbox/build.rs b/codex-rs/linux-sandbox/build.rs new file mode 100644 index 00000000000..9394981f702 --- /dev/null +++ b/codex-rs/linux-sandbox/build.rs @@ -0,0 +1,112 @@ +use std::env; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + // Tell rustc/clippy that this is an expected cfg value. + println!("cargo:rustc-check-cfg=cfg(vendored_bwrap_available)"); + + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + if target_os != "linux" { + return; + } + + // Opt-in: do not attempt to fetch/compile bwrap unless explicitly enabled. + let enable_ffi = matches!(env::var("CODEX_BWRAP_ENABLE_FFI"), Ok(value) if value == "1"); + if !enable_ffi { + return; + } + + if let Err(err) = try_build_vendored_bwrap() { + // Keep normal builds working even if the experiment fails. + println!("cargo:warning=build-time bubblewrap disabled: {err}"); + } +} + +fn try_build_vendored_bwrap() -> Result<(), String> { + let out_dir = PathBuf::from(env::var("OUT_DIR").map_err(|err| err.to_string())?); + let src_dir = resolve_bwrap_source_dir(&out_dir)?; + + let libcap = pkg_config::Config::new() + .probe("libcap") + .map_err(|err| format!("libcap not available via pkg-config: {err}"))?; + + let config_h = out_dir.join("config.h"); + std::fs::write( + &config_h, + "#pragma once\n#define PACKAGE_STRING \"bubblewrap built at codex build-time\"\n", + ) + .map_err(|err| format!("failed to write {}: {err}", config_h.display()))?; + + let mut build = cc::Build::new(); + build + .file(src_dir.join("bubblewrap.c")) + .file(src_dir.join("bind-mount.c")) + .file(src_dir.join("network.c")) + .file(src_dir.join("utils.c")) + .include(&out_dir) + .include(&src_dir) + .define("_GNU_SOURCE", None) + // Rename `main` so we can call it via FFI. + .define("main", Some("bwrap_main")); + + for include_path in libcap.include_paths { + build.include(include_path); + } + + build.compile("build_time_bwrap"); + println!("cargo:rustc-cfg=vendored_bwrap_available"); + Ok(()) +} + +/// Resolve the bubblewrap source directory used for build-time compilation. +/// +/// Priority: +/// 1. `CODEX_BWRAP_SOURCE_DIR` points at an existing bubblewrap checkout. +/// 2. `CODEX_BWRAP_FETCH=1` triggers a build-time shallow git clone into +/// `OUT_DIR`. +fn resolve_bwrap_source_dir(out_dir: &Path) -> Result { + if let Ok(path) = env::var("CODEX_BWRAP_SOURCE_DIR") { + let src_dir = PathBuf::from(path); + if src_dir.exists() { + return Ok(src_dir); + } + return Err(format!( + "CODEX_BWRAP_SOURCE_DIR was set but does not exist: {}", + src_dir.display() + )); + } + + let fetch = matches!(env::var("CODEX_BWRAP_FETCH"), Ok(value) if value == "1"); + if !fetch { + return Err( + "no bwrap source available: set CODEX_BWRAP_SOURCE_DIR or CODEX_BWRAP_FETCH=1" + .to_string(), + ); + } + + let fetch_ref = env::var("CODEX_BWRAP_FETCH_REF").unwrap_or_else(|_| "v0.11.0".to_string()); + let src_dir = out_dir.join("bubblewrap-src"); + if src_dir.exists() { + return Ok(src_dir); + } + + let status = Command::new("git") + .arg("clone") + .arg("--depth") + .arg("1") + .arg("--branch") + .arg(&fetch_ref) + .arg("https://github.com/containers/bubblewrap") + .arg(&src_dir) + .status() + .map_err(|err| format!("failed to spawn git clone: {err}"))?; + if status.success() { + return Ok(src_dir); + } + + Err(format!( + "git clone bubblewrap ({fetch_ref}) failed with status: {status}" + )) +} diff --git a/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh b/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh new file mode 100644 index 00000000000..ba6da626174 --- /dev/null +++ b/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Linux sandbox smoke test script. +# +# This is designed for Linux devboxes where bwrap is available. It builds the +# codex-linux-sandbox binary and runs a small matrix of behavior checks: +# - workspace writes succeed +# - protected paths (.git, .codex) remain read-only +# - writes outside allowed roots fail +# - network_access=false blocks outbound sockets +# +# Usage: +# codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh +# +# Optional env vars: +# CODEX_LINUX_SANDBOX_NO_PROC=1 # default: 1 (pass --no-proc for bwrap suite) +# CODEX_LINUX_SANDBOX_DEBUG=1 # default: 0 (pass debug env var through) +# CODEX_LINUX_SANDBOX_USE_BWRAP=1 # default: 1 (run the bwrap suite) +# CODEX_LINUX_SANDBOX_USE_LEGACY=1 # default: 1 (run the legacy suite) +# CODEX_LINUX_SANDBOX_USE_VENDORED=1 # default: 0 (use build-time bwrap FFI) +# CODEX_LINUX_SANDBOX_BWRAP_PATH # default: $(command -v bwrap) + +if [[ "$(uname -s)" != "Linux" ]]; then + echo "This script is intended to run on Linux." >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +CODEX_RS_DIR="${REPO_ROOT}/codex-rs" + +NO_PROC="${CODEX_LINUX_SANDBOX_NO_PROC:-1}" +DEBUG="${CODEX_LINUX_SANDBOX_DEBUG:-0}" +USE_BWRAP_SUITE="${CODEX_LINUX_SANDBOX_USE_BWRAP:-1}" +USE_LEGACY_SUITE="${CODEX_LINUX_SANDBOX_USE_LEGACY:-1}" +USE_VENDORED="${CODEX_LINUX_SANDBOX_USE_VENDORED:-0}" + +BWRAP_PATH="" +if [[ "${USE_VENDORED}" != "1" ]]; then + BWRAP_PATH="${CODEX_LINUX_SANDBOX_BWRAP_PATH:-$(command -v bwrap || true)}" + if [[ -z "${BWRAP_PATH}" ]]; then + echo "bubblewrap (bwrap) is required but was not found on PATH." >&2 + exit 1 + fi +fi + +SANDBOX_BIN="${CODEX_RS_DIR}/target/debug/codex-linux-sandbox" +tmp_root="" + +build_binary() { + echo "==> Building codex-linux-sandbox" + (cd "${CODEX_RS_DIR}" && cargo build -p codex-linux-sandbox >/dev/null) +} + +policy_json() { + local network_access="$1" + printf '{"type":"workspace-write","writable_roots":[],"network_access":%s}' "${network_access}" +} + +run_sandbox() { + local network_access="$1" + local use_bwrap="$2" + shift + shift + + local no_proc_flag=() + if [[ "${NO_PROC}" == "1" && "${use_bwrap}" == "1" ]]; then + no_proc_flag=(--no-proc) + fi + + local debug_env=() + if [[ "${DEBUG}" == "1" ]]; then + debug_env=(env CODEX_LINUX_SANDBOX_DEBUG=1) + fi + + local bwrap_flag=() + if [[ "${use_bwrap}" == "1" ]]; then + if [[ "${USE_VENDORED}" == "1" ]]; then + bwrap_flag=(--use-vendored-bwrap) + else + bwrap_flag=(--bwrap-path "${BWRAP_PATH}") + fi + fi + + "${debug_env[@]}" "${SANDBOX_BIN}" \ + --sandbox-policy-cwd "${REPO_ROOT}" \ + --sandbox-policy "$(policy_json "${network_access}")" \ + "${bwrap_flag[@]}" \ + "${no_proc_flag[@]}" \ + -- "$@" +} + +expect_success() { + local label="$1" + local network_access="$2" + local use_bwrap="$3" + shift + shift + shift + echo "==> ${label}" + if run_sandbox "${network_access}" "${use_bwrap}" "$@"; then + echo " PASS" + else + echo " FAIL (expected success)" >&2 + exit 1 + fi +} + +expect_failure() { + local label="$1" + local network_access="$2" + local use_bwrap="$3" + shift + shift + shift + echo "==> ${label}" + if run_sandbox "${network_access}" "${use_bwrap}" "$@"; then + echo " FAIL (expected failure)" >&2 + exit 1 + else + echo " PASS (failed as expected)" + fi +} + +run_suite() { + local suite_name="$1" + local use_bwrap="$2" + + echo + echo "==== Suite: ${suite_name} (use_bwrap=${use_bwrap}) ====" + + # Create a disposable writable root for workspace-write checks. + if [[ -n "${tmp_root:-}" ]]; then + rm -rf -- "${tmp_root}" + fi + tmp_root="$(mktemp -d "${REPO_ROOT}/.codex-sandbox-test.XXXXXX")" + trap 'rm -rf -- "${tmp_root:-}"' EXIT + + mkdir -p "${REPO_ROOT}/.codex" + + expect_success \ + "workspace write succeeds inside repo" \ + true \ + "${use_bwrap}" \ + /usr/bin/bash -lc "cd '${tmp_root}' && touch OK_IN_WORKSPACE" + + expect_failure \ + "writes outside allowed roots fail" \ + true \ + "${use_bwrap}" \ + /usr/bin/bash -lc "touch /etc/SHOULD_FAIL" + + # Only the bwrap suite enforces `.git` and `.codex` as read-only. + if [[ "${use_bwrap}" == "1" ]]; then + expect_failure \ + ".git and .codex remain read-only (bwrap)" \ + true \ + "${use_bwrap}" \ + /usr/bin/bash -lc "cd '${REPO_ROOT}' && touch .git/SHOULD_FAIL && touch .codex/SHOULD_FAIL" + else + expect_success \ + ".git and .codex are NOT protected in legacy landlock path" \ + true \ + "${use_bwrap}" \ + /usr/bin/bash -lc "cd '${REPO_ROOT}' && mkdir -p .codex && touch .git/SHOULD_SUCCEED && touch .codex/SHOULD_SUCCEED" + fi + + expect_failure \ + "network_access=false blocks outbound sockets" \ + false \ + "${use_bwrap}" \ + /usr/bin/bash -lc "exec 3<>/dev/tcp/1.1.1.1/443" +} + +main() { + build_binary + + if [[ "${USE_BWRAP_SUITE}" == "1" ]]; then + run_suite "bwrap opt-in" "1" + fi + + if [[ "${USE_LEGACY_SUITE}" == "1" ]]; then + run_suite "legacy default" "0" + fi + + echo + echo "All requested linux-sandbox suites passed." +} + +main "$@" diff --git a/codex-rs/linux-sandbox/src/bwrap.rs b/codex-rs/linux-sandbox/src/bwrap.rs new file mode 100644 index 00000000000..c1e0732e081 --- /dev/null +++ b/codex-rs/linux-sandbox/src/bwrap.rs @@ -0,0 +1,291 @@ +//! Bubblewrap-based filesystem sandboxing for Linux. +//! +//! This module mirrors the semantics used by the macOS Seatbelt sandbox: +//! - the filesystem is read-only by default, +//! - explicit writable roots are layered on top, and +//! - sensitive subpaths such as `.git` and `.codex` remain read-only even when +//! their parent root is writable. +//! +//! The overall Linux sandbox is composed of: +//! - seccomp + `PR_SET_NO_NEW_PRIVS` applied in-process, and +//! - bubblewrap used to construct the filesystem view before exec. +use std::collections::BTreeSet; +use std::path::Path; +use std::path::PathBuf; + +use codex_core::error::CodexErr; +use codex_core::error::Result; +use codex_core::protocol::SandboxPolicy; +use codex_core::protocol::WritableRoot; + +/// Options that control how bubblewrap is invoked. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct BwrapOptions { + /// Whether to mount a fresh `/proc` inside the PID namespace. + /// + /// This is the secure default, but some restrictive container environments + /// deny `--proc /proc` even when PID namespaces are available. + pub mount_proc: bool, +} + +impl Default for BwrapOptions { + fn default() -> Self { + Self { mount_proc: true } + } +} + +/// Wrap a command with bubblewrap so the filesystem is read-only by default, +/// with explicit writable roots and read-only subpaths layered afterward. +/// +/// When the policy grants full disk write access, this returns `command` +/// unchanged so we avoid unnecessary sandboxing overhead. +pub(crate) fn create_bwrap_command_args( + command: Vec, + sandbox_policy: &SandboxPolicy, + cwd: &Path, + options: BwrapOptions, + bwrap_path: Option<&Path>, +) -> Result> { + if sandbox_policy.has_full_disk_write_access() { + return Ok(command); + } + + let bwrap_path = match bwrap_path { + Some(path) => { + if path.exists() { + path.to_path_buf() + } else { + return Err(CodexErr::UnsupportedOperation(format!( + "bubblewrap (bwrap) not found at configured path: {}", + path.display() + ))); + } + } + None => which::which("bwrap").map_err(|err| { + CodexErr::UnsupportedOperation(format!("bubblewrap (bwrap) not found on PATH: {err}")) + })?, + }; + + let mut args = Vec::new(); + args.push(path_to_string(&bwrap_path)); + args.extend(create_bwrap_flags(command, sandbox_policy, cwd, options)?); + Ok(args) +} + +/// Doc-hidden helper that builds bubblewrap arguments without a program path. +/// +/// This is intended for experiments where we call a build-time bubblewrap +/// `main` symbol via FFI rather than exec'ing the `bwrap` binary. The caller +/// is responsible for providing a suitable `argv[0]`. +#[doc(hidden)] +pub(crate) fn create_bwrap_command_args_vendored( + command: Vec, + sandbox_policy: &SandboxPolicy, + cwd: &Path, + options: BwrapOptions, +) -> Result> { + if sandbox_policy.has_full_disk_write_access() { + return Ok(command); + } + + create_bwrap_flags(command, sandbox_policy, cwd, options) +} + +/// Build the bubblewrap flags (everything after `argv[0]`). +fn create_bwrap_flags( + command: Vec, + sandbox_policy: &SandboxPolicy, + cwd: &Path, + options: BwrapOptions, +) -> Result> { + let mut args = Vec::new(); + args.push("--new-session".to_string()); + args.push("--die-with-parent".to_string()); + args.extend(create_filesystem_args(sandbox_policy, cwd)?); + // Isolate the PID namespace. + args.push("--unshare-pid".to_string()); + // Mount a fresh /proc unless the caller explicitly disables it. + if options.mount_proc { + args.push("--proc".to_string()); + args.push("/proc".to_string()); + } + args.push("--".to_string()); + args.extend(command); + Ok(args) +} + +/// Build the bubblewrap filesystem mounts for a given sandbox policy. +/// +/// The mount order is important: +/// 1. `--ro-bind / /` makes the entire filesystem read-only. +/// 2. `--bind ` re-enables writes for allowed roots. +/// 3. `--ro-bind ` re-applies read-only protections under +/// those writable roots so protected subpaths win. +/// 4. `--dev-bind /dev/null /dev/null` preserves the common sink even under a +/// read-only root. +fn create_filesystem_args(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Result> { + let writable_roots = sandbox_policy.get_writable_roots_with_cwd(cwd); + ensure_mount_targets_exist(&writable_roots)?; + + let mut args = Vec::new(); + + // Read-only root, then selectively re-enable writes. + args.push("--ro-bind".to_string()); + args.push("/".to_string()); + args.push("/".to_string()); + + for writable_root in &writable_roots { + let root = writable_root.root.as_path(); + args.push("--bind".to_string()); + args.push(path_to_string(root)); + args.push(path_to_string(root)); + } + + // Re-apply read-only subpaths after the writable binds so they win. + let allowed_write_paths: Vec = writable_roots + .iter() + .map(|writable_root| writable_root.root.as_path().to_path_buf()) + .collect(); + + for subpath in collect_read_only_subpaths(&writable_roots) { + if let Some(symlink_path) = find_symlink_in_path(&subpath, &allowed_write_paths) { + args.push("--ro-bind".to_string()); + args.push("/dev/null".to_string()); + args.push(path_to_string(&symlink_path)); + continue; + } + + if !subpath.exists() { + if let Some(first_missing) = find_first_non_existent_component(&subpath) + && is_within_allowed_write_paths(&first_missing, &allowed_write_paths) + { + args.push("--ro-bind".to_string()); + args.push("/dev/null".to_string()); + args.push(path_to_string(&first_missing)); + } + continue; + } + + if is_within_allowed_write_paths(&subpath, &allowed_write_paths) { + args.push("--ro-bind".to_string()); + args.push(path_to_string(&subpath)); + args.push(path_to_string(&subpath)); + } + } + + // Ensure `/dev/null` remains usable regardless of the root bind. + args.push("--dev-bind".to_string()); + args.push("/dev/null".to_string()); + args.push("/dev/null".to_string()); + + Ok(args) +} + +/// Collect unique read-only subpaths across all writable roots. +fn collect_read_only_subpaths(writable_roots: &[WritableRoot]) -> Vec { + let mut subpaths: BTreeSet = BTreeSet::new(); + for writable_root in writable_roots { + for subpath in &writable_root.read_only_subpaths { + subpaths.insert(subpath.as_path().to_path_buf()); + } + } + subpaths.into_iter().collect() +} + +/// Validate that writable roots exist before constructing mounts. +/// +/// Bubblewrap requires bind mount targets to exist. We fail fast with a clear +/// error so callers can present an actionable message. +fn ensure_mount_targets_exist(writable_roots: &[WritableRoot]) -> Result<()> { + for writable_root in writable_roots { + let root = writable_root.root.as_path(); + if !root.exists() { + return Err(CodexErr::UnsupportedOperation(format!( + "Sandbox expected writable root {root}, but it does not exist.", + root = root.display() + ))); + } + } + Ok(()) +} + +fn path_to_string(path: &Path) -> String { + path.to_string_lossy().to_string() +} + +/// Returns true when `path` is under any allowed writable root. +fn is_within_allowed_write_paths(path: &Path, allowed_write_paths: &[PathBuf]) -> bool { + allowed_write_paths + .iter() + .any(|root| path.starts_with(root)) +} + +/// Find the first symlink along `target_path` that is also under a writable root. +/// +/// This blocks symlink replacement attacks where a protected path is a symlink +/// inside a writable root (e.g., `.codex -> ./decoy`). In that case we mount +/// `/dev/null` on the symlink itself to prevent rewiring it. +fn find_symlink_in_path(target_path: &Path, allowed_write_paths: &[PathBuf]) -> Option { + let mut current = PathBuf::new(); + + for component in target_path.components() { + use std::path::Component; + match component { + Component::RootDir => { + current.push(Path::new("/")); + continue; + } + Component::CurDir => continue, + Component::ParentDir => { + current.pop(); + continue; + } + Component::Normal(part) => current.push(part), + Component::Prefix(_) => continue, + } + + let metadata = match std::fs::symlink_metadata(¤t) { + Ok(metadata) => metadata, + Err(_) => break, + }; + + if metadata.file_type().is_symlink() + && is_within_allowed_write_paths(¤t, allowed_write_paths) + { + return Some(current); + } + } + + None +} + +/// Find the first missing path component while walking `target_path`. +/// +/// Mounting `/dev/null` on the first missing component prevents the sandboxed +/// process from creating the protected path hierarchy. +fn find_first_non_existent_component(target_path: &Path) -> Option { + let mut current = PathBuf::new(); + + for component in target_path.components() { + use std::path::Component; + match component { + Component::RootDir => { + current.push(Path::new("/")); + continue; + } + Component::CurDir => continue, + Component::ParentDir => { + current.pop(); + continue; + } + Component::Normal(part) => current.push(part), + Component::Prefix(_) => continue, + } + + if !current.exists() { + return Some(current); + } + } + + None +} diff --git a/codex-rs/linux-sandbox/src/lib.rs b/codex-rs/linux-sandbox/src/lib.rs index 38ecb8cc219..3347f3f92d7 100644 --- a/codex-rs/linux-sandbox/src/lib.rs +++ b/codex-rs/linux-sandbox/src/lib.rs @@ -1,9 +1,16 @@ +//! Linux sandbox helper entry point. +//! +//! On Linux, `codex-linux-sandbox` applies: +//! - in-process restrictions (`no_new_privs` + seccomp), and +//! - bubblewrap for filesystem isolation. +#[cfg(target_os = "linux")] +mod bwrap; #[cfg(target_os = "linux")] mod landlock; #[cfg(target_os = "linux")] mod linux_run_main; #[cfg(target_os = "linux")] -mod mounts; +mod vendored_bwrap; #[cfg(target_os = "linux")] pub fn run_main() -> ! { diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index c4b0767fb63..346ab7735dd 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -1,10 +1,19 @@ use clap::Parser; use std::ffi::CString; +use std::path::Path; use std::path::PathBuf; +use crate::bwrap::BwrapOptions; +use crate::bwrap::create_bwrap_command_args; +use crate::bwrap::create_bwrap_command_args_vendored; use crate::landlock::apply_sandbox_policy_to_current_thread; +use crate::vendored_bwrap::exec_vendored_bwrap; #[derive(Debug, Parser)] +/// CLI surface for the Linux sandbox helper. +/// +/// The type name remains `LandlockCommand` for compatibility with existing +/// wiring, but the filesystem sandbox now uses bubblewrap. pub struct LandlockCommand { /// It is possible that the cwd used in the context of the sandbox policy /// is different from the cwd of the process to spawn. @@ -14,26 +23,182 @@ pub struct LandlockCommand { #[arg(long = "sandbox-policy")] pub sandbox_policy: codex_core::protocol::SandboxPolicy, - /// Full command args to run under landlock. + /// Opt-in: use the bubblewrap-based Linux sandbox pipeline. + /// + /// When not set, we fall back to the legacy Landlock + mount pipeline. + #[arg(long = "use-bwrap-sandbox", hide = true, default_value_t = false)] + pub use_bwrap_sandbox: bool, + + /// Optional explicit path to the `bwrap` binary to use. + /// + /// When provided, this implies bubblewrap opt-in and avoids PATH lookups. + #[arg(long = "bwrap-path", hide = true)] + pub bwrap_path: Option, + + /// Experimental: call a build-time bubblewrap `main()` via FFI. + /// + /// This is opt-in and only works when the build script compiles bwrap. + #[arg(long = "use-vendored-bwrap", hide = true, default_value_t = false)] + pub use_vendored_bwrap: bool, + + /// Internal: apply seccomp and `no_new_privs` in the already-sandboxed + /// process, then exec the user command. + /// + /// This exists so we can run bubblewrap first (which may rely on setuid) + /// and only tighten with seccomp after the filesystem view is established. + #[arg(long = "apply-seccomp-then-exec", hide = true, default_value_t = false)] + pub apply_seccomp_then_exec: bool, + + /// When set, skip mounting a fresh `/proc` even though PID isolation is + /// still enabled. This is primarily intended for restrictive container + /// environments that deny `--proc /proc`. + #[arg(long = "no-proc", default_value_t = false)] + pub no_proc: bool, + + /// Full command args to run under the Linux sandbox helper. #[arg(trailing_var_arg = true)] pub command: Vec, } +/// Entry point for the Linux sandbox helper. +/// +/// The sequence is: +/// 1. When needed, wrap the command with bubblewrap to construct the +/// filesystem view. +/// 2. Apply in-process restrictions (no_new_privs + seccomp). +/// 3. `execvp` into the final command. pub fn run_main() -> ! { let LandlockCommand { sandbox_policy_cwd, sandbox_policy, + use_bwrap_sandbox, + bwrap_path, + use_vendored_bwrap, + apply_seccomp_then_exec, + no_proc, command, } = LandlockCommand::parse(); - - if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) { - panic!("error running landlock: {e:?}"); - } + let use_bwrap_sandbox = use_bwrap_sandbox || bwrap_path.is_some() || use_vendored_bwrap; if command.is_empty() { panic!("No command specified to execute."); } + // Inner stage: apply seccomp/no_new_privs after bubblewrap has already + // established the filesystem view. + if apply_seccomp_then_exec { + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) + { + panic!("error applying Linux sandbox restrictions: {e:?}"); + } + exec_or_panic(command); + } + + let command = if sandbox_policy.has_full_disk_write_access() { + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) + { + panic!("error applying Linux sandbox restrictions: {e:?}"); + } + command + } else if use_bwrap_sandbox { + // Outer stage: bubblewrap first, then re-enter this binary in the + // sandboxed environment to apply seccomp. + let inner = build_inner_seccomp_command( + &sandbox_policy_cwd, + &sandbox_policy, + use_bwrap_sandbox, + bwrap_path.as_deref(), + command, + ); + let options = BwrapOptions { + mount_proc: !no_proc, + }; + if use_vendored_bwrap { + let mut argv0 = bwrap_path + .as_deref() + .map(|path| path.to_string_lossy().to_string()) + .unwrap_or_else(|| "bwrap".to_string()); + if argv0.is_empty() { + argv0 = "bwrap".to_string(); + } + + let mut argv = vec![argv0]; + argv.extend( + create_bwrap_command_args_vendored( + inner, + &sandbox_policy, + &sandbox_policy_cwd, + options, + ) + .unwrap_or_else(|err| { + panic!("error building build-time bubblewrap command: {err:?}") + }), + ); + exec_vendored_bwrap(argv); + } + ensure_bwrap_available(bwrap_path.as_deref()); + create_bwrap_command_args( + inner, + &sandbox_policy, + &sandbox_policy_cwd, + options, + bwrap_path.as_deref(), + ) + .unwrap_or_else(|err| panic!("error building bubblewrap command: {err:?}")) + } else { + // Legacy path: Landlock enforcement only. + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, true) + { + panic!("error applying legacy Linux sandbox restrictions: {e:?}"); + } + command + }; + + exec_or_panic(command); +} + +/// Build the inner command that applies seccomp after bubblewrap. +fn build_inner_seccomp_command( + sandbox_policy_cwd: &Path, + sandbox_policy: &codex_core::protocol::SandboxPolicy, + use_bwrap_sandbox: bool, + bwrap_path: Option<&Path>, + command: Vec, +) -> Vec { + let current_exe = match std::env::current_exe() { + Ok(path) => path, + Err(err) => panic!("failed to resolve current executable path: {err}"), + }; + let policy_json = match serde_json::to_string(sandbox_policy) { + Ok(json) => json, + Err(err) => panic!("failed to serialize sandbox policy: {err}"), + }; + + let mut inner = vec![ + current_exe.to_string_lossy().to_string(), + "--sandbox-policy-cwd".to_string(), + sandbox_policy_cwd.to_string_lossy().to_string(), + "--sandbox-policy".to_string(), + policy_json, + ]; + if use_bwrap_sandbox { + inner.push("--use-bwrap-sandbox".to_string()); + inner.push("--apply-seccomp-then-exec".to_string()); + } + if let Some(bwrap_path) = bwrap_path { + inner.push("--bwrap-path".to_string()); + inner.push(bwrap_path.to_string_lossy().to_string()); + } + inner.push("--".to_string()); + inner.extend(command); + inner +} + +/// Exec the provided argv, panicking with context if it fails. +fn exec_or_panic(command: Vec) -> ! { #[expect(clippy::expect_used)] let c_command = CString::new(command[0].as_str()).expect("Failed to convert command to CString"); @@ -54,3 +219,33 @@ pub fn run_main() -> ! { let err = std::io::Error::last_os_error(); panic!("Failed to execvp {}: {err}", command[0].as_str()); } + +/// Ensure the `bwrap` binary is available when the sandbox needs it. +fn ensure_bwrap_available(bwrap_path: Option<&Path>) { + if let Some(path) = bwrap_path { + if path.exists() { + return; + } + panic!( + "bubblewrap (bwrap) is required for Linux filesystem sandboxing but was not found at the configured path: {}\n\ +Install it and retry. Examples:\n\ +- Debian/Ubuntu: apt-get install bubblewrap\n\ +- Fedora/RHEL: dnf install bubblewrap\n\ +- Arch: pacman -S bubblewrap\n\ +If you are running the Codex Node package, ensure bwrap is installed on the host system.", + path.display() + ); + } + if which::which("bwrap").is_ok() { + return; + } + + panic!( + "bubblewrap (bwrap) is required for Linux filesystem sandboxing but was not found on PATH.\n\ +Install it and retry. Examples:\n\ +- Debian/Ubuntu: apt-get install bubblewrap\n\ +- Fedora/RHEL: dnf install bubblewrap\n\ +- Arch: pacman -S bubblewrap\n\ +If you are running the Codex Node package, ensure bwrap is installed on the host system." + ); +} diff --git a/codex-rs/linux-sandbox/src/vendored_bwrap.rs b/codex-rs/linux-sandbox/src/vendored_bwrap.rs new file mode 100644 index 00000000000..664450af5a9 --- /dev/null +++ b/codex-rs/linux-sandbox/src/vendored_bwrap.rs @@ -0,0 +1,50 @@ +//! Build-time bubblewrap entrypoint. +//! +//! This module is intentionally behind a build-time opt-in. When enabled, the +//! build script compiles bubblewrap's C sources and exposes a `bwrap_main` +//! symbol that we can call via FFI. + +#[cfg(vendored_bwrap_available)] +mod imp { + use std::ffi::CString; + use std::os::raw::c_char; + + unsafe extern "C" { + fn bwrap_main(argc: libc::c_int, argv: *const *const c_char) -> libc::c_int; + } + + /// Execute the build-time bubblewrap `main` function with the given argv. + pub(crate) fn exec_vendored_bwrap(argv: Vec) -> ! { + let mut cstrings: Vec = Vec::with_capacity(argv.len()); + for arg in &argv { + match CString::new(arg.as_str()) { + Ok(value) => cstrings.push(value), + Err(err) => panic!("failed to convert argv to CString: {err}"), + } + } + + let mut argv_ptrs: Vec<*const c_char> = cstrings.iter().map(|arg| arg.as_ptr()).collect(); + argv_ptrs.push(std::ptr::null()); + + // SAFETY: We provide a null-terminated argv vector whose pointers + // remain valid for the duration of the call. + let exit_code = unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) }; + std::process::exit(exit_code); + } +} + +#[cfg(not(vendored_bwrap_available))] +mod imp { + /// Panics with a clear error when the build-time bwrap path is not enabled. + pub(crate) fn exec_vendored_bwrap(_argv: Vec) -> ! { + panic!( + "build-time bubblewrap is not available in this build.\n\ +To enable it on Linux:\n\ +- set CODEX_BWRAP_ENABLE_FFI=1\n\ +- and either set CODEX_BWRAP_SOURCE_DIR to a bubblewrap checkout,\n\ + or set CODEX_BWRAP_FETCH=1 (and optionally CODEX_BWRAP_FETCH_REF)." + ); + } +} + +pub(crate) use imp::exec_vendored_bwrap; From c1e4692a851d3a92b61ccf39590638c1e89dac80 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Fri, 30 Jan 2026 13:25:11 -0800 Subject: [PATCH 2/7] feat(linux-sandbox): vendor bubblewrap sources --- codex-rs/linux-sandbox/build.rs | 43 +- codex-rs/vendor/bubblewrap/.dir-locals.el | 1 + codex-rs/vendor/bubblewrap/.editorconfig | 5 + .../bubblewrap/.github/workflows/check.yml | 105 + codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md | 3 + codex-rs/vendor/bubblewrap/COPYING | 481 +++ codex-rs/vendor/bubblewrap/LICENSE | 1 + codex-rs/vendor/bubblewrap/NEWS.md | 51 + codex-rs/vendor/bubblewrap/README.md | 255 ++ codex-rs/vendor/bubblewrap/SECURITY.md | 42 + codex-rs/vendor/bubblewrap/bind-mount.c | 598 +++ codex-rs/vendor/bubblewrap/bind-mount.h | 54 + codex-rs/vendor/bubblewrap/bubblewrap.c | 3641 +++++++++++++++++ codex-rs/vendor/bubblewrap/bubblewrap.jpg | Bin 0 -> 40239 bytes codex-rs/vendor/bubblewrap/bwrap.xml | 648 +++ codex-rs/vendor/bubblewrap/ci/builddeps.sh | 107 + .../vendor/bubblewrap/ci/enable-userns.sh | 6 + .../vendor/bubblewrap/completions/bash/bwrap | 80 + .../bubblewrap/completions/bash/meson.build | 35 + .../vendor/bubblewrap/completions/meson.build | 7 + .../vendor/bubblewrap/completions/zsh/_bwrap | 115 + .../bubblewrap/completions/zsh/meson.build | 7 + .../bubblewrap/demos/bubblewrap-shell.sh | 34 + .../vendor/bubblewrap/demos/flatpak-run.sh | 65 + codex-rs/vendor/bubblewrap/demos/flatpak.bpf | Bin 0 -> 744 bytes .../bubblewrap/demos/userns-block-fd.py | 39 + codex-rs/vendor/bubblewrap/meson.build | 171 + codex-rs/vendor/bubblewrap/meson_options.txt | 73 + codex-rs/vendor/bubblewrap/network.c | 199 + codex-rs/vendor/bubblewrap/network.h | 22 + .../bubblewrap/packaging/bubblewrap.spec | 48 + .../vendor/bubblewrap/release-checklist.md | 18 + .../vendor/bubblewrap/tests/libtest-core.sh | 190 + codex-rs/vendor/bubblewrap/tests/libtest.sh | 115 + codex-rs/vendor/bubblewrap/tests/meson.build | 72 + codex-rs/vendor/bubblewrap/tests/test-run.sh | 692 ++++ .../vendor/bubblewrap/tests/test-seccomp.py | 635 +++ .../bubblewrap/tests/test-specifying-pidns.sh | 28 + .../tests/test-specifying-userns.sh | 28 + codex-rs/vendor/bubblewrap/tests/test-utils.c | 247 ++ .../vendor/bubblewrap/tests/try-syscall.c | 180 + .../tests/use-as-subproject/.gitignore | 2 + .../bubblewrap/tests/use-as-subproject/README | 3 + .../use-as-subproject/assert-correct-rpath.py | 26 + .../tests/use-as-subproject/config.h | 1 + .../tests/use-as-subproject/dummy-config.h.in | 1 + .../tests/use-as-subproject/meson.build | 20 + codex-rs/vendor/bubblewrap/uncrustify.cfg | 136 + codex-rs/vendor/bubblewrap/uncrustify.sh | 2 + codex-rs/vendor/bubblewrap/utils.c | 1080 +++++ codex-rs/vendor/bubblewrap/utils.h | 217 + 51 files changed, 10597 insertions(+), 32 deletions(-) create mode 100644 codex-rs/vendor/bubblewrap/.dir-locals.el create mode 100644 codex-rs/vendor/bubblewrap/.editorconfig create mode 100644 codex-rs/vendor/bubblewrap/.github/workflows/check.yml create mode 100644 codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md create mode 100644 codex-rs/vendor/bubblewrap/COPYING create mode 120000 codex-rs/vendor/bubblewrap/LICENSE create mode 100644 codex-rs/vendor/bubblewrap/NEWS.md create mode 100644 codex-rs/vendor/bubblewrap/README.md create mode 100644 codex-rs/vendor/bubblewrap/SECURITY.md create mode 100644 codex-rs/vendor/bubblewrap/bind-mount.c create mode 100644 codex-rs/vendor/bubblewrap/bind-mount.h create mode 100644 codex-rs/vendor/bubblewrap/bubblewrap.c create mode 100644 codex-rs/vendor/bubblewrap/bubblewrap.jpg create mode 100644 codex-rs/vendor/bubblewrap/bwrap.xml create mode 100755 codex-rs/vendor/bubblewrap/ci/builddeps.sh create mode 100755 codex-rs/vendor/bubblewrap/ci/enable-userns.sh create mode 100644 codex-rs/vendor/bubblewrap/completions/bash/bwrap create mode 100644 codex-rs/vendor/bubblewrap/completions/bash/meson.build create mode 100644 codex-rs/vendor/bubblewrap/completions/meson.build create mode 100644 codex-rs/vendor/bubblewrap/completions/zsh/_bwrap create mode 100644 codex-rs/vendor/bubblewrap/completions/zsh/meson.build create mode 100755 codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh create mode 100755 codex-rs/vendor/bubblewrap/demos/flatpak-run.sh create mode 100644 codex-rs/vendor/bubblewrap/demos/flatpak.bpf create mode 100755 codex-rs/vendor/bubblewrap/demos/userns-block-fd.py create mode 100644 codex-rs/vendor/bubblewrap/meson.build create mode 100644 codex-rs/vendor/bubblewrap/meson_options.txt create mode 100644 codex-rs/vendor/bubblewrap/network.c create mode 100644 codex-rs/vendor/bubblewrap/network.h create mode 100644 codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec create mode 100644 codex-rs/vendor/bubblewrap/release-checklist.md create mode 100644 codex-rs/vendor/bubblewrap/tests/libtest-core.sh create mode 100644 codex-rs/vendor/bubblewrap/tests/libtest.sh create mode 100644 codex-rs/vendor/bubblewrap/tests/meson.build create mode 100755 codex-rs/vendor/bubblewrap/tests/test-run.sh create mode 100755 codex-rs/vendor/bubblewrap/tests/test-seccomp.py create mode 100755 codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh create mode 100755 codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh create mode 100644 codex-rs/vendor/bubblewrap/tests/test-utils.c create mode 100644 codex-rs/vendor/bubblewrap/tests/try-syscall.c create mode 100644 codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore create mode 100644 codex-rs/vendor/bubblewrap/tests/use-as-subproject/README create mode 100755 codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py create mode 100644 codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h create mode 100644 codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in create mode 100644 codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build create mode 100644 codex-rs/vendor/bubblewrap/uncrustify.cfg create mode 100755 codex-rs/vendor/bubblewrap/uncrustify.sh create mode 100644 codex-rs/vendor/bubblewrap/utils.c create mode 100644 codex-rs/vendor/bubblewrap/utils.h diff --git a/codex-rs/linux-sandbox/build.rs b/codex-rs/linux-sandbox/build.rs index 9394981f702..95503badbe8 100644 --- a/codex-rs/linux-sandbox/build.rs +++ b/codex-rs/linux-sandbox/build.rs @@ -1,7 +1,6 @@ use std::env; use std::path::Path; use std::path::PathBuf; -use std::process::Command; fn main() { // Tell rustc/clippy that this is an expected cfg value. @@ -25,8 +24,10 @@ fn main() { } fn try_build_vendored_bwrap() -> Result<(), String> { + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|err| err.to_string())?); let out_dir = PathBuf::from(env::var("OUT_DIR").map_err(|err| err.to_string())?); - let src_dir = resolve_bwrap_source_dir(&out_dir)?; + let src_dir = resolve_bwrap_source_dir(&manifest_dir)?; let libcap = pkg_config::Config::new() .probe("libcap") @@ -64,9 +65,8 @@ fn try_build_vendored_bwrap() -> Result<(), String> { /// /// Priority: /// 1. `CODEX_BWRAP_SOURCE_DIR` points at an existing bubblewrap checkout. -/// 2. `CODEX_BWRAP_FETCH=1` triggers a build-time shallow git clone into -/// `OUT_DIR`. -fn resolve_bwrap_source_dir(out_dir: &Path) -> Result { +/// 2. The vendored bubblewrap tree under `codex-rs/vendor/bubblewrap`. +fn resolve_bwrap_source_dir(manifest_dir: &Path) -> Result { if let Ok(path) = env::var("CODEX_BWRAP_SOURCE_DIR") { let src_dir = PathBuf::from(path); if src_dir.exists() { @@ -78,35 +78,14 @@ fn resolve_bwrap_source_dir(out_dir: &Path) -> Result { )); } - let fetch = matches!(env::var("CODEX_BWRAP_FETCH"), Ok(value) if value == "1"); - if !fetch { - return Err( - "no bwrap source available: set CODEX_BWRAP_SOURCE_DIR or CODEX_BWRAP_FETCH=1" - .to_string(), - ); - } - - let fetch_ref = env::var("CODEX_BWRAP_FETCH_REF").unwrap_or_else(|_| "v0.11.0".to_string()); - let src_dir = out_dir.join("bubblewrap-src"); - if src_dir.exists() { - return Ok(src_dir); - } - - let status = Command::new("git") - .arg("clone") - .arg("--depth") - .arg("1") - .arg("--branch") - .arg(&fetch_ref) - .arg("https://github.com/containers/bubblewrap") - .arg(&src_dir) - .status() - .map_err(|err| format!("failed to spawn git clone: {err}"))?; - if status.success() { - return Ok(src_dir); + let vendor_dir = manifest_dir.join("../vendor/bubblewrap"); + if vendor_dir.exists() { + return Ok(vendor_dir); } Err(format!( - "git clone bubblewrap ({fetch_ref}) failed with status: {status}" + "expected vendored bubblewrap at {}, but it was not found.\n\ +Set CODEX_BWRAP_SOURCE_DIR to an existing checkout or vendor bubblewrap under codex-rs/vendor.", + vendor_dir.display() )) } diff --git a/codex-rs/vendor/bubblewrap/.dir-locals.el b/codex-rs/vendor/bubblewrap/.dir-locals.el new file mode 100644 index 00000000000..35140408952 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/.dir-locals.el @@ -0,0 +1 @@ +((c-mode . ((indent-tabs-mode . nil) (c-file-style . "gnu")))) diff --git a/codex-rs/vendor/bubblewrap/.editorconfig b/codex-rs/vendor/bubblewrap/.editorconfig new file mode 100644 index 00000000000..89742b6981f --- /dev/null +++ b/codex-rs/vendor/bubblewrap/.editorconfig @@ -0,0 +1,5 @@ +[*.[ch]] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +indent_brace_style = gnu diff --git a/codex-rs/vendor/bubblewrap/.github/workflows/check.yml b/codex-rs/vendor/bubblewrap/.github/workflows/check.yml new file mode 100644 index 00000000000..8a747d52ca0 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/.github/workflows/check.yml @@ -0,0 +1,105 @@ +name: CI checks + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + meson: + name: Build with Meson and gcc, and test + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + - name: Install build-dependencies + run: sudo ./ci/builddeps.sh + - name: Enable user namespaces + run: sudo ./ci/enable-userns.sh + - name: Create logs dir + run: mkdir test-logs + - name: setup + run: | + meson _build + env: + CFLAGS: >- + -O2 + -Wp,-D_FORTIFY_SOURCE=2 + -fsanitize=address + -fsanitize=undefined + - name: compile + run: ninja -C _build -v + - name: smoke-test + run: | + set -x + ./_build/bwrap --bind / / --tmpfs /tmp true + env: + ASAN_OPTIONS: detect_leaks=0 + - name: test + run: | + BWRAP_MUST_WORK=1 meson test -C _build + env: + ASAN_OPTIONS: detect_leaks=0 + - name: Collect overall test logs on failure + if: failure() + run: mv _build/meson-logs/testlog.txt test-logs/ || true + - name: install + run: | + DESTDIR="$(pwd)/DESTDIR" meson install -C _build + ( cd DESTDIR && find -ls ) + - name: dist + run: | + BWRAP_MUST_WORK=1 meson dist -C _build + - name: Collect dist test logs on failure + if: failure() + run: mv _build/meson-private/dist-build/meson-logs/testlog.txt test-logs/disttestlog.txt || true + - name: use as subproject + run: | + mkdir tests/use-as-subproject/subprojects + tar -C tests/use-as-subproject/subprojects -xf _build/meson-dist/bubblewrap-*.tar.xz + mv tests/use-as-subproject/subprojects/bubblewrap-* tests/use-as-subproject/subprojects/bubblewrap + ( cd tests/use-as-subproject && meson _build ) + ninja -C tests/use-as-subproject/_build -v + meson test -C tests/use-as-subproject/_build + DESTDIR="$(pwd)/DESTDIR-as-subproject" meson install -C tests/use-as-subproject/_build + ( cd DESTDIR-as-subproject && find -ls ) + test -x DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap + test ! -e DESTDIR-as-subproject/usr/local/bin/bwrap + test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap + tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap + - name: Upload test logs + uses: actions/upload-artifact@v4 + if: failure() || cancelled() + with: + name: test logs + path: test-logs + + clang: + name: Build with clang and analyze + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: + - cpp + steps: + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + - name: Check out + uses: actions/checkout@v4 + - name: Install build-dependencies + run: sudo ./ci/builddeps.sh --clang + - run: meson build -Dselinux=enabled + env: + CC: clang + CFLAGS: >- + -O2 + -Werror=unused-variable + - run: meson compile -C build + - name: CodeQL analysis + uses: github/codeql-action/analyze@v2 diff --git a/codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md b/codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md new file mode 100644 index 00000000000..8c417a60968 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## The bubblewrap Project Community Code of Conduct + +The bubblewrap project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/HEAD/CODE-OF-CONDUCT.md). diff --git a/codex-rs/vendor/bubblewrap/COPYING b/codex-rs/vendor/bubblewrap/COPYING new file mode 100644 index 00000000000..5bc8fb2c8f7 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/COPYING @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/codex-rs/vendor/bubblewrap/LICENSE b/codex-rs/vendor/bubblewrap/LICENSE new file mode 120000 index 00000000000..d24842f3cdc --- /dev/null +++ b/codex-rs/vendor/bubblewrap/LICENSE @@ -0,0 +1 @@ +COPYING \ No newline at end of file diff --git a/codex-rs/vendor/bubblewrap/NEWS.md b/codex-rs/vendor/bubblewrap/NEWS.md new file mode 100644 index 00000000000..da232c4bd77 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/NEWS.md @@ -0,0 +1,51 @@ +bubblewrap 0.11.0 +================= + +Released: 2024-10-30 + +Dependencies: + + * Remove the Autotools build system. Meson ≥ 0.49.0 is now required + at build-time. (#625, Hugo Osvaldo Barrera) + + * For users of bash-completion, bash-completion ≥ 2.10 is recommended. + With older bash-completion, bubblewrap might install completions + outside its `${prefix}` unless overridden with `-Dbash_completion_dir=…`. + +Enhancements: + + * New `--overlay`, `--tmp-overlay`, `--ro-overlay` and `--overlay-src` + options allow creation of overlay mounts. + This feature is not available when bubblewrap is installed setuid. + (#412, #663; Ryan Hendrickson, William Manley, Simon McVittie) + + * New `--level-prefix` option produces output that can be parsed by + tools like `logger --prio-prefix` and `systemd-cat --level-prefix=1` + (#646, Simon McVittie) + +Bug fixes: + + * Handle `EINTR` when doing I/O on files or sockets (#657, Simon McVittie) + + * Don't make assumptions about alignment of socket control message data + (#637, Simon McVittie) + + * Silence some Meson deprecation warnings (#647, @Sertonix) + + * Update URLs in documentation to https (#566, @TotalCaesar659) + + * Improve tests' compatibility with busybox (#627, @Sertonix) + + * Improve compatibility with Meson < 1.3.0 (#664, Simon McVittie) + +Internal changes: + + * Consistently use `` for booleans (#660, Simon McVittie) + + * Avoid `-Wshadow` compiler warnings (#661, Simon McVittie) + + * Update Github Actions configuration (#658, Simon McVittie) + +---- + +See also diff --git a/codex-rs/vendor/bubblewrap/README.md b/codex-rs/vendor/bubblewrap/README.md new file mode 100644 index 00000000000..c16cd7d89ad --- /dev/null +++ b/codex-rs/vendor/bubblewrap/README.md @@ -0,0 +1,255 @@ +Bubblewrap +========== + +Many container runtime tools like `systemd-nspawn`, `docker`, +etc. focus on providing infrastructure for system administrators and +orchestration tools (e.g. Kubernetes) to run containers. + +These tools are not suitable to give to unprivileged users, because it +is trivial to turn such access into a fully privileged root shell +on the host. + +User namespaces +--------------- + +There is an effort in the Linux kernel called +[user namespaces](https://www.google.com/search?q=user+namespaces+site%3Ahttps%3A%2F%2Flwn.net) +which attempts to allow unprivileged users to use container features. +While significant progress has been made, there are +[still concerns](https://lwn.net/Articles/673597/) about it, and +it is not available to unprivileged users in several production distributions +such as CentOS/Red Hat Enterprise Linux 7, Debian Jessie, etc. + +See for example +[CVE-2016-3135](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-3135) +which is a local root vulnerability introduced by userns. +[This March 2016 post](https://lkml.org/lkml/2016/3/9/555) has some +more discussion. + +Bubblewrap could be viewed as setuid implementation of a *subset* of +user namespaces. Emphasis on subset - specifically relevant to the +above CVE, bubblewrap does not allow control over iptables. + +The original bubblewrap code existed before user namespaces - it inherits code from +[xdg-app helper](https://cgit.freedesktop.org/xdg-app/xdg-app/tree/common/xdg-app-helper.c?id=4c3bf179e2e4a2a298cd1db1d045adaf3f564532) +which in turn distantly derives from +[linux-user-chroot](https://git.gnome.org/browse/linux-user-chroot). + +System security +--------------- + +The maintainers of this tool believe that it does not, even when used +in combination with typical software installed on that distribution, +allow privilege escalation. It may increase the ability of a logged +in user to perform denial of service attacks, however. + +In particular, bubblewrap uses `PR_SET_NO_NEW_PRIVS` to turn off +setuid binaries, which is the [traditional way](https://en.wikipedia.org/wiki/Chroot#Limitations) to get out of things +like chroots. + +Sandbox security +---------------- + +bubblewrap is a tool for constructing sandbox environments. +bubblewrap is not a complete, ready-made sandbox with a specific security +policy. + +Some of bubblewrap's use-cases want a security boundary between the sandbox +and the real system; other use-cases want the ability to change the layout of +the filesystem for processes inside the sandbox, but do not aim to be a +security boundary. +As a result, the level of protection between the sandboxed processes and +the host system is entirely determined by the arguments passed to +bubblewrap. + +Whatever program constructs the command-line arguments for bubblewrap +(often a larger framework like Flatpak, libgnome-desktop, sandwine +or an ad-hoc script) is responsible for defining its own security model, +and choosing appropriate bubblewrap command-line arguments to implement +that security model. + +Some aspects of sandbox security that require particular care are described +in the [Limitations](#limitations) section below. + +Users +----- + +This program can be shared by all container tools which perform +non-root operation, such as: + + - [Flatpak](https://www.flatpak.org) + - [rpm-ostree unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209) + - [bwrap-oci](https://github.com/projectatomic/bwrap-oci) + +We would also like to see this be available in Kubernetes/OpenShift +clusters. Having the ability for unprivileged users to use container +features would make it significantly easier to do interactive +debugging scenarios and the like. + +Installation +------------ + +bubblewrap is available in the package repositories of the most Linux distributions +and can be installed from there. + +If you need to build bubblewrap from source, you can do this with meson: + +```sh +meson _builddir +meson compile -C _builddir +meson test -C _builddir +meson install -C _builddir +``` + +Usage +----- + +bubblewrap works by creating a new, completely empty, mount +namespace where the root is on a tmpfs that is invisible from the +host, and will be automatically cleaned up when the last process +exits. You can then use commandline options to construct the root +filesystem and process environment and command to run in the +namespace. + +There's a larger [demo script](./demos/bubblewrap-shell.sh) in the +source code, but here's a trimmed down version which runs +a new shell reusing the host's `/usr`. + +``` +bwrap \ + --ro-bind /usr /usr \ + --symlink usr/lib64 /lib64 \ + --proc /proc \ + --dev /dev \ + --unshare-pid \ + --new-session \ + bash +``` + +This is an incomplete example, but useful for purposes of +illustration. More often, rather than creating a container using the +host's filesystem tree, you want to target a chroot. There, rather +than creating the symlink `lib64 -> usr/lib64` in the tmpfs, you might +have already created it in the target rootfs. + +Sandboxing +---------- + +The goal of bubblewrap is to run an application in a sandbox, where it +has restricted access to parts of the operating system or user data +such as the home directory. + +bubblewrap always creates a new mount namespace, and the user can specify +exactly what parts of the filesystem should be visible in the sandbox. +Any such directories you specify mounted `nodev` by default, and can be made readonly. + +Additionally you can use these kernel features: + +User namespaces ([CLONE_NEWUSER](https://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the +sandbox. You can also change what the value of uid/gid should be in the sandbox. + +IPC namespaces ([CLONE_NEWIPC](https://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the +different forms of IPCs, like SysV shared memory and semaphores. + +PID namespaces ([CLONE_NEWPID](https://linux.die.net/man/2/clone)): The sandbox will not see any processes outside the sandbox. Additionally, bubblewrap will run a trivial pid1 inside your container to handle the requirements of reaping children in the sandbox. This avoids what is known now as the [Docker pid 1 problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/). + + +Network namespaces ([CLONE_NEWNET](https://linux.die.net/man/2/clone)): The sandbox will not see the network. Instead it will have its own network namespace with only a loopback device. + +UTS namespace ([CLONE_NEWUTS](https://linux.die.net/man/2/clone)): The sandbox will have its own hostname. + +Seccomp filters: You can pass in seccomp filters that limit which syscalls can be done in the sandbox. For more information, see [Seccomp](https://en.wikipedia.org/wiki/Seccomp). + +Limitations +----------- + +As noted in the [Sandbox security](#sandbox-security) section above, +the level of protection between the sandboxed processes and the host system +is entirely determined by the arguments passed to bubblewrap. +Some aspects that require special care are noted here. + +- If you are not filtering out `TIOCSTI` commands using seccomp filters, +argument `--new-session` is needed to protect against out-of-sandbox +command execution +(see [CVE-2017-5226](https://github.com/containers/bubblewrap/issues/142)). + +- Everything mounted into the sandbox can potentially be used to escalate +privileges. +For example, if you bind a D-Bus socket into the sandbox, it can be used to +execute commands via systemd. You can use +[xdg-dbus-proxy](https://github.com/flatpak/xdg-dbus-proxy) to filter +D-Bus communication. + +- Some applications deploy their own sandboxing mechanisms, and these can be +restricted by the constraints imposed by bubblewrap's sandboxing. +For example, some web browsers which configure their child proccesses via +seccomp to not have access to the filesystem. If you limit the syscalls and +don't allow the seccomp syscall, a browser cannot apply these restrictions. +Similarly, if these rules were compiled into a file that is not available in +the sandbox, the browser cannot load these rules from this file and cannot +apply these restrictions. + +Related project comparison: Firejail +------------------------------------ + +[Firejail](https://github.com/netblue30/firejail/tree/HEAD/src/firejail) +is similar to Flatpak before bubblewrap was split out in that it combines +a setuid tool with a lot of desktop-specific sandboxing features. For +example, Firejail knows about Pulseaudio, whereas bubblewrap does not. + +The bubblewrap authors believe it's much easier to audit a small +setuid program, and keep features such as Pulseaudio filtering as an +unprivileged process, as now occurs in Flatpak. + +Also, @cgwalters thinks trying to +[whitelist file paths](https://github.com/netblue30/firejail/blob/37a5a3545ef6d8d03dad8bbd888f53e13274c9e5/src/firejail/fs_whitelist.c#L176) +is a bad idea given the myriad ways users have to manipulate paths, +and the myriad ways in which system administrators may configure a +system. The bubblewrap approach is to only retain a few specific +Linux capabilities such as `CAP_SYS_ADMIN`, but to always access the +filesystem as the invoking uid. This entirely closes +[TOCTTOU attacks](https://cwe.mitre.org/data/definitions/367.html) and +such. + +Related project comparison: Sandstorm.io +---------------------------------------- + +[Sandstorm.io](https://sandstorm.io/) requires unprivileged user +namespaces to set up its sandbox, though it could easily be adapted +to operate in a setuid mode as well. @cgwalters believes their code is +fairly good, but it could still make sense to unify on bubblewrap. +However, @kentonv (of Sandstorm) feels that while this makes sense +in principle, the switching cost outweighs the practical benefits for +now. This decision could be re-evaluated in the future, but it is not +being actively pursued today. + +Related project comparison: runc/binctr +---------------------------------------- + +[runC](https://github.com/opencontainers/runc) is currently working on +supporting [rootless containers](https://github.com/opencontainers/runc/pull/774), +without needing `setuid` or any other privileges during installation of +runC (using unprivileged user namespaces rather than `setuid`), +creation, and management of containers. However, the standard mode of +using runC is similar to [systemd nspawn](https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html) +in that it is tooling intended to be invoked by root. + +The bubblewrap authors believe that runc and systemd-nspawn are not +designed to be made setuid, and are distant from supporting such a mode. +However with rootless containers, runC will be able to fulfill certain usecases +that bubblewrap supports (with the added benefit of being a standardised and +complete OCI runtime). + +[binctr](https://github.com/jfrazelle/binctr) is just a wrapper for +runC, so inherits all of its design tradeoffs. + +What's with the name?! +---------------------- + +The name bubblewrap was chosen to convey that this +tool runs as the parent of the application (so wraps it in some sense) and creates +a protective layer (the sandbox) around it. + +![](bubblewrap.jpg) + +(Bubblewrap cat by [dancing_stupidity](https://www.flickr.com/photos/27549668@N03/)) diff --git a/codex-rs/vendor/bubblewrap/SECURITY.md b/codex-rs/vendor/bubblewrap/SECURITY.md new file mode 100644 index 00000000000..0ddfc6c873e --- /dev/null +++ b/codex-rs/vendor/bubblewrap/SECURITY.md @@ -0,0 +1,42 @@ +## Security and Disclosure Information Policy for the bubblewrap Project + +The bubblewrap Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/HEAD/SECURITY.md) for the Containers Projects. + +### System security + +If bubblewrap is setuid root, then the goal is that it does not allow +a malicious local user to do anything that would not have been possible +on a kernel that allows unprivileged users to create new user namespaces. +For example, [CVE-2020-5291](https://github.com/containers/bubblewrap/security/advisories/GHSA-j2qp-rvxj-43vj) +was treated as a security vulnerability in bubblewrap. + +If bubblewrap is not setuid root, then it is not a security boundary +between the user and the OS, because anything bubblewrap could do, a +malicious user could equally well do by writing their own tool equivalent +to bubblewrap. + +### Sandbox security + +bubblewrap is a toolkit for constructing sandbox environments. +bubblewrap is not a complete, ready-made sandbox with a specific security +policy. + +Some of bubblewrap's use-cases want a security boundary between the sandbox +and the real system; other use-cases want the ability to change the layout of +the filesystem for processes inside the sandbox, but do not aim to be a +security boundary. +As a result, the level of protection between the sandboxed processes and +the host system is entirely determined by the arguments passed to +bubblewrap. + +Whatever program constructs the command-line arguments for bubblewrap +(often a larger framework like Flatpak, libgnome-desktop, sandwine +or an ad-hoc script) is responsible for defining its own security model, +and choosing appropriate bubblewrap command-line arguments to implement +that security model. + +For example, +[CVE-2017-5226](https://github.com/flatpak/flatpak/security/advisories/GHSA-7gfv-rvfx-h87x) +(in which a Flatpak app could send input to a parent terminal using the +`TIOCSTI` ioctl) is considered to be a Flatpak vulnerability, not a +bubblewrap vulnerability. diff --git a/codex-rs/vendor/bubblewrap/bind-mount.c b/codex-rs/vendor/bubblewrap/bind-mount.c new file mode 100644 index 00000000000..a2e1ac6c407 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/bind-mount.c @@ -0,0 +1,598 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "config.h" + +#include + +#include "utils.h" +#include "bind-mount.h" + +static char * +skip_token (char *line, bool eat_whitespace) +{ + while (*line != ' ' && *line != '\n') + line++; + + if (eat_whitespace && *line == ' ') + line++; + + return line; +} + +static char * +unescape_inline (char *escaped) +{ + char *unescaped, *res; + const char *end; + + res = escaped; + end = escaped + strlen (escaped); + + unescaped = escaped; + while (escaped < end) + { + if (*escaped == '\\') + { + *unescaped++ = + ((escaped[1] - '0') << 6) | + ((escaped[2] - '0') << 3) | + ((escaped[3] - '0') << 0); + escaped += 4; + } + else + { + *unescaped++ = *escaped++; + } + } + *unescaped = 0; + return res; +} + +static bool +match_token (const char *token, const char *token_end, const char *str) +{ + while (token != token_end && *token == *str) + { + token++; + str++; + } + if (token == token_end) + return *str == 0; + + return false; +} + +static unsigned long +decode_mountoptions (const char *options) +{ + const char *token, *end_token; + int i; + unsigned long flags = 0; + static const struct { int flag; + const char *name; + } flags_data[] = { + { 0, "rw" }, + { MS_RDONLY, "ro" }, + { MS_NOSUID, "nosuid" }, + { MS_NODEV, "nodev" }, + { MS_NOEXEC, "noexec" }, + { MS_NOATIME, "noatime" }, + { MS_NODIRATIME, "nodiratime" }, + { MS_RELATIME, "relatime" }, + { 0, NULL } + }; + + token = options; + do + { + end_token = strchr (token, ','); + if (end_token == NULL) + end_token = token + strlen (token); + + for (i = 0; flags_data[i].name != NULL; i++) + { + if (match_token (token, end_token, flags_data[i].name)) + { + flags |= flags_data[i].flag; + break; + } + } + + if (*end_token != 0) + token = end_token + 1; + else + token = NULL; + } + while (token != NULL); + + return flags; +} + +typedef struct MountInfo MountInfo; +struct MountInfo { + char *mountpoint; + unsigned long options; +}; + +typedef MountInfo *MountTab; + +static void +mount_tab_free (MountTab tab) +{ + int i; + + for (i = 0; tab[i].mountpoint != NULL; i++) + free (tab[i].mountpoint); + free (tab); +} + +static inline void +cleanup_mount_tabp (void *p) +{ + void **pp = (void **) p; + + if (*pp) + mount_tab_free ((MountTab)*pp); +} + +#define cleanup_mount_tab __attribute__((cleanup (cleanup_mount_tabp))) + +typedef struct MountInfoLine MountInfoLine; +struct MountInfoLine { + const char *mountpoint; + const char *options; + bool covered; + int id; + int parent_id; + MountInfoLine *first_child; + MountInfoLine *next_sibling; +}; + +static unsigned int +count_lines (const char *data) +{ + unsigned int count = 0; + const char *p = data; + + while (*p != 0) + { + if (*p == '\n') + count++; + p++; + } + + /* If missing final newline, add one */ + if (p > data && *(p-1) != '\n') + count++; + + return count; +} + +static int +count_mounts (MountInfoLine *line) +{ + MountInfoLine *child; + int res = 0; + + if (!line->covered) + res += 1; + + child = line->first_child; + while (child != NULL) + { + res += count_mounts (child); + child = child->next_sibling; + } + + return res; +} + +static MountInfo * +collect_mounts (MountInfo *info, MountInfoLine *line) +{ + MountInfoLine *child; + + if (!line->covered) + { + info->mountpoint = xstrdup (line->mountpoint); + info->options = decode_mountoptions (line->options); + info ++; + } + + child = line->first_child; + while (child != NULL) + { + info = collect_mounts (info, child); + child = child->next_sibling; + } + + return info; +} + +static MountTab +parse_mountinfo (int proc_fd, + const char *root_mount) +{ + cleanup_free char *mountinfo = NULL; + cleanup_free MountInfoLine *lines = NULL; + cleanup_free MountInfoLine **by_id = NULL; + cleanup_mount_tab MountTab mount_tab = NULL; + MountInfo *end_tab; + int n_mounts; + char *line; + unsigned int i; + int max_id; + unsigned int n_lines; + int root; + + mountinfo = load_file_at (proc_fd, "self/mountinfo"); + if (mountinfo == NULL) + die_with_error ("Can't open /proc/self/mountinfo"); + + n_lines = count_lines (mountinfo); + lines = xcalloc (n_lines, sizeof (MountInfoLine)); + + max_id = 0; + line = mountinfo; + i = 0; + root = -1; + while (*line != 0) + { + int rc, consumed = 0; + unsigned int maj, min; + char *end; + char *rest; + char *mountpoint; + char *mountpoint_end; + char *options; + char *options_end; + char *next_line; + + assert (i < n_lines); + + end = strchr (line, '\n'); + if (end != NULL) + { + *end = 0; + next_line = end + 1; + } + else + next_line = line + strlen (line); + + rc = sscanf (line, "%d %d %u:%u %n", &lines[i].id, &lines[i].parent_id, &maj, &min, &consumed); + if (rc != 4) + die ("Can't parse mountinfo line"); + rest = line + consumed; + + rest = skip_token (rest, true); /* mountroot */ + mountpoint = rest; + rest = skip_token (rest, false); /* mountpoint */ + mountpoint_end = rest++; + options = rest; + rest = skip_token (rest, false); /* vfs options */ + options_end = rest; + + *mountpoint_end = 0; + lines[i].mountpoint = unescape_inline (mountpoint); + + *options_end = 0; + lines[i].options = options; + + if (lines[i].id > max_id) + max_id = lines[i].id; + if (lines[i].parent_id > max_id) + max_id = lines[i].parent_id; + + if (path_equal (lines[i].mountpoint, root_mount)) + root = i; + + i++; + line = next_line; + } + assert (i == n_lines); + + if (root == -1) + { + mount_tab = xcalloc (1, sizeof (MountInfo)); + return steal_pointer (&mount_tab); + } + + by_id = xcalloc (max_id + 1, sizeof (MountInfoLine*)); + for (i = 0; i < n_lines; i++) + by_id[lines[i].id] = &lines[i]; + + for (i = 0; i < n_lines; i++) + { + MountInfoLine *this = &lines[i]; + MountInfoLine *parent = by_id[this->parent_id]; + MountInfoLine **to_sibling; + MountInfoLine *sibling; + bool covered = false; + + if (!has_path_prefix (this->mountpoint, root_mount)) + continue; + + if (parent == NULL) + continue; + + if (strcmp (parent->mountpoint, this->mountpoint) == 0) + parent->covered = true; + + to_sibling = &parent->first_child; + sibling = parent->first_child; + while (sibling != NULL) + { + /* If this mountpoint is a path prefix of the sibling, + * say this->mp=/foo/bar and sibling->mp=/foo, then it is + * covered by the sibling, and we drop it. */ + if (has_path_prefix (this->mountpoint, sibling->mountpoint)) + { + covered = true; + break; + } + + /* If the sibling is a path prefix of this mount point, + * say this->mp=/foo and sibling->mp=/foo/bar, then the sibling + * is covered, and we drop it. + */ + if (has_path_prefix (sibling->mountpoint, this->mountpoint)) + *to_sibling = sibling->next_sibling; + else + to_sibling = &sibling->next_sibling; + sibling = sibling->next_sibling; + } + + if (covered) + continue; + + *to_sibling = this; + } + + n_mounts = count_mounts (&lines[root]); + mount_tab = xcalloc (n_mounts + 1, sizeof (MountInfo)); + + end_tab = collect_mounts (&mount_tab[0], &lines[root]); + assert (end_tab == &mount_tab[n_mounts]); + + return steal_pointer (&mount_tab); +} + +bind_mount_result +bind_mount (int proc_fd, + const char *src, + const char *dest, + bind_option_t options, + char **failing_path) +{ + bool readonly = (options & BIND_READONLY) != 0; + bool devices = (options & BIND_DEVICES) != 0; + bool recursive = (options & BIND_RECURSIVE) != 0; + unsigned long current_flags, new_flags; + cleanup_mount_tab MountTab mount_tab = NULL; + cleanup_free char *resolved_dest = NULL; + cleanup_free char *dest_proc = NULL; + cleanup_free char *oldroot_dest_proc = NULL; + cleanup_free char *kernel_case_combination = NULL; + cleanup_fd int dest_fd = -1; + int i; + + if (src) + { + if (mount (src, dest, NULL, MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0) + return BIND_MOUNT_ERROR_MOUNT; + } + + /* The mount operation will resolve any symlinks in the destination + path, so to find it in the mount table we need to do that too. */ + resolved_dest = realpath (dest, NULL); + if (resolved_dest == NULL) + return BIND_MOUNT_ERROR_REALPATH_DEST; + + dest_fd = TEMP_FAILURE_RETRY (open (resolved_dest, O_PATH | O_CLOEXEC)); + if (dest_fd < 0) + { + if (failing_path != NULL) + *failing_path = steal_pointer (&resolved_dest); + + return BIND_MOUNT_ERROR_REOPEN_DEST; + } + + /* If we are in a case-insensitive filesystem, mountinfo might contain a + * different case combination of the path we requested to mount. + * This is due to the fact that the kernel, as of the beginning of 2021, + * populates mountinfo with whatever case combination first appeared in the + * dcache; kernel developers plan to change this in future so that it + * reflects the on-disk encoding instead. + * To avoid throwing an error when this happens, we use readlink() result + * instead of the provided @root_mount, so that we can compare the mountinfo + * entries with the same case combination that the kernel is expected to + * use. */ + dest_proc = xasprintf ("/proc/self/fd/%d", dest_fd); + oldroot_dest_proc = get_oldroot_path (dest_proc); + kernel_case_combination = readlink_malloc (oldroot_dest_proc); + if (kernel_case_combination == NULL) + { + if (failing_path != NULL) + *failing_path = steal_pointer (&resolved_dest); + + return BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD; + } + + mount_tab = parse_mountinfo (proc_fd, kernel_case_combination); + if (mount_tab[0].mountpoint == NULL) + { + if (failing_path != NULL) + *failing_path = steal_pointer (&kernel_case_combination); + + errno = EINVAL; + return BIND_MOUNT_ERROR_FIND_DEST_MOUNT; + } + + assert (path_equal (mount_tab[0].mountpoint, kernel_case_combination)); + current_flags = mount_tab[0].options; + new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0); + if (new_flags != current_flags && + mount ("none", resolved_dest, + NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0) + { + if (failing_path != NULL) + *failing_path = steal_pointer (&resolved_dest); + + return BIND_MOUNT_ERROR_REMOUNT_DEST; + } + + /* We need to work around the fact that a bind mount does not apply the flags, so we need to manually + * apply the flags to all submounts in the recursive case. + * Note: This does not apply the flags to mounts which are later propagated into this namespace. + */ + if (recursive) + { + for (i = 1; mount_tab[i].mountpoint != NULL; i++) + { + current_flags = mount_tab[i].options; + new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0); + if (new_flags != current_flags && + mount ("none", mount_tab[i].mountpoint, + NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0) + { + /* If we can't read the mountpoint we can't remount it, but that should + be safe to ignore because its not something the user can access. */ + if (errno != EACCES) + { + if (failing_path != NULL) + *failing_path = xstrdup (mount_tab[i].mountpoint); + + return BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT; + } + } + } + } + + return BIND_MOUNT_SUCCESS; +} + +/** + * Return a string representing bind_mount_result, like strerror(). + * If want_errno_p is non-NULL, *want_errno_p is used to indicate whether + * it would make sense to print strerror(saved_errno). + */ +static char * +bind_mount_result_to_string (bind_mount_result res, + const char *failing_path, + bool *want_errno_p) +{ + char *string = NULL; + bool want_errno = true; + + switch (res) + { + case BIND_MOUNT_ERROR_MOUNT: + string = xstrdup ("Unable to mount source on destination"); + break; + + case BIND_MOUNT_ERROR_REALPATH_DEST: + string = xstrdup ("realpath(destination)"); + break; + + case BIND_MOUNT_ERROR_REOPEN_DEST: + string = xasprintf ("open(\"%s\", O_PATH)", failing_path); + break; + + case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD: + string = xasprintf ("readlink(/proc/self/fd/N) for \"%s\"", failing_path); + break; + + case BIND_MOUNT_ERROR_FIND_DEST_MOUNT: + string = xasprintf ("Unable to find \"%s\" in mount table", failing_path); + want_errno = false; + break; + + case BIND_MOUNT_ERROR_REMOUNT_DEST: + string = xasprintf ("Unable to remount destination \"%s\" with correct flags", + failing_path); + break; + + case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT: + string = xasprintf ("Unable to apply mount flags: remount \"%s\"", + failing_path); + break; + + case BIND_MOUNT_SUCCESS: + string = xstrdup ("Success"); + break; + + default: + string = xstrdup ("(unknown/invalid bind_mount_result)"); + break; + } + + if (want_errno_p != NULL) + *want_errno_p = want_errno; + + return string; +} + +void +die_with_bind_result (bind_mount_result res, + int saved_errno, + const char *failing_path, + const char *format, + ...) +{ + va_list args; + bool want_errno = true; + char *message; + + if (bwrap_level_prefix) + fprintf (stderr, "<%d>", LOG_ERR); + + fprintf (stderr, "bwrap: "); + + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); + + message = bind_mount_result_to_string (res, failing_path, &want_errno); + fprintf (stderr, ": %s", message); + /* message is leaked, but we're exiting unsuccessfully anyway, so ignore */ + + if (want_errno) + { + switch (res) + { + case BIND_MOUNT_ERROR_MOUNT: + case BIND_MOUNT_ERROR_REMOUNT_DEST: + case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT: + fprintf (stderr, ": %s", mount_strerror (saved_errno)); + break; + + case BIND_MOUNT_ERROR_REALPATH_DEST: + case BIND_MOUNT_ERROR_REOPEN_DEST: + case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD: + case BIND_MOUNT_ERROR_FIND_DEST_MOUNT: + case BIND_MOUNT_SUCCESS: + default: + fprintf (stderr, ": %s", strerror (saved_errno)); + } + } + + fprintf (stderr, "\n"); + exit (1); +} diff --git a/codex-rs/vendor/bubblewrap/bind-mount.h b/codex-rs/vendor/bubblewrap/bind-mount.h new file mode 100644 index 00000000000..8a361fbd5fc --- /dev/null +++ b/codex-rs/vendor/bubblewrap/bind-mount.h @@ -0,0 +1,54 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#pragma once + +#include "utils.h" + +typedef enum { + BIND_READONLY = (1 << 0), + BIND_DEVICES = (1 << 2), + BIND_RECURSIVE = (1 << 3), +} bind_option_t; + +typedef enum +{ + BIND_MOUNT_SUCCESS = 0, + BIND_MOUNT_ERROR_MOUNT, + BIND_MOUNT_ERROR_REALPATH_DEST, + BIND_MOUNT_ERROR_REOPEN_DEST, + BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD, + BIND_MOUNT_ERROR_FIND_DEST_MOUNT, + BIND_MOUNT_ERROR_REMOUNT_DEST, + BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT, +} bind_mount_result; + +bind_mount_result bind_mount (int proc_fd, + const char *src, + const char *dest, + bind_option_t options, + char **failing_path); + +void die_with_bind_result (bind_mount_result res, + int saved_errno, + const char *failing_path, + const char *format, + ...) + __attribute__((__noreturn__)) + __attribute__((format (printf, 4, 5))); diff --git a/codex-rs/vendor/bubblewrap/bubblewrap.c b/codex-rs/vendor/bubblewrap/bubblewrap.c new file mode 100644 index 00000000000..69d319b7a39 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/bubblewrap.c @@ -0,0 +1,3641 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "network.h" +#include "bind-mount.h" + +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ +#endif + +/* We limit the size of a tmpfs to half the architecture's address space, + * to avoid hitting arbitrary limits in the kernel. + * For example, on at least one x86_64 machine, the actual limit seems to be + * 2^64 - 2^12. */ +#define MAX_TMPFS_BYTES ((size_t) (SIZE_MAX >> 1)) + +/* Globals to avoid having to use getuid(), since the uid/gid changes during runtime */ +static uid_t real_uid; +static gid_t real_gid; +static uid_t overflow_uid; +static gid_t overflow_gid; +static bool is_privileged; /* See acquire_privs() */ +static const char *argv0; +static const char *host_tty_dev; +static int proc_fd = -1; +static const char *opt_exec_label = NULL; +static const char *opt_file_label = NULL; +static bool opt_as_pid_1; + +static const char *opt_argv0 = NULL; +static const char *opt_chdir_path = NULL; +static bool opt_assert_userns_disabled = false; +static bool opt_disable_userns = false; +static bool opt_unshare_user = false; +static bool opt_unshare_user_try = false; +static bool opt_unshare_pid = false; +static bool opt_unshare_ipc = false; +static bool opt_unshare_net = false; +static bool opt_unshare_uts = false; +static bool opt_unshare_cgroup = false; +static bool opt_unshare_cgroup_try = false; +static bool opt_needs_devpts = false; +static bool opt_new_session = false; +static bool opt_die_with_parent = false; +static uid_t opt_sandbox_uid = -1; +static gid_t opt_sandbox_gid = -1; +static int opt_sync_fd = -1; +static int opt_block_fd = -1; +static int opt_userns_block_fd = -1; +static int opt_info_fd = -1; +static int opt_json_status_fd = -1; +static int opt_seccomp_fd = -1; +static const char *opt_sandbox_hostname = NULL; +static char *opt_args_data = NULL; /* owned */ +static int opt_userns_fd = -1; +static int opt_userns2_fd = -1; +static int opt_pidns_fd = -1; +static int opt_tmp_overlay_count = 0; +static int next_perms = -1; +static size_t next_size_arg = 0; +static int next_overlay_src_count = 0; + +#define CAP_TO_MASK_0(x) (1L << ((x) & 31)) +#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) + +typedef struct _NsInfo NsInfo; + +struct _NsInfo { + const char *name; + bool *do_unshare; + ino_t id; +}; + +static NsInfo ns_infos[] = { + {"cgroup", &opt_unshare_cgroup, 0}, + {"ipc", &opt_unshare_ipc, 0}, + {"mnt", NULL, 0}, + {"net", &opt_unshare_net, 0}, + {"pid", &opt_unshare_pid, 0}, + /* user namespace info omitted because it + * is not (yet) valid when we obtain the + * namespace info (get un-shared later) */ + {"uts", &opt_unshare_uts, 0}, + {NULL, NULL, 0} +}; + +typedef enum { + SETUP_BIND_MOUNT, + SETUP_RO_BIND_MOUNT, + SETUP_DEV_BIND_MOUNT, + SETUP_OVERLAY_MOUNT, + SETUP_TMP_OVERLAY_MOUNT, + SETUP_RO_OVERLAY_MOUNT, + SETUP_OVERLAY_SRC, + SETUP_MOUNT_PROC, + SETUP_MOUNT_DEV, + SETUP_MOUNT_TMPFS, + SETUP_MOUNT_MQUEUE, + SETUP_MAKE_DIR, + SETUP_MAKE_FILE, + SETUP_MAKE_BIND_FILE, + SETUP_MAKE_RO_BIND_FILE, + SETUP_MAKE_SYMLINK, + SETUP_REMOUNT_RO_NO_RECURSIVE, + SETUP_SET_HOSTNAME, + SETUP_CHMOD, +} SetupOpType; + +typedef enum { + NO_CREATE_DEST = (1 << 0), + ALLOW_NOTEXIST = (1 << 1), +} SetupOpFlag; + +typedef struct _SetupOp SetupOp; + +struct _SetupOp +{ + SetupOpType type; + const char *source; + const char *dest; + int fd; + SetupOpFlag flags; + int perms; + size_t size; /* number of bytes, zero means unset/default */ + SetupOp *next; +}; + +typedef struct _LockFile LockFile; + +struct _LockFile +{ + const char *path; + int fd; + LockFile *next; +}; + +enum { + PRIV_SEP_OP_DONE, + PRIV_SEP_OP_BIND_MOUNT, + PRIV_SEP_OP_OVERLAY_MOUNT, + PRIV_SEP_OP_PROC_MOUNT, + PRIV_SEP_OP_TMPFS_MOUNT, + PRIV_SEP_OP_DEVPTS_MOUNT, + PRIV_SEP_OP_MQUEUE_MOUNT, + PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, + PRIV_SEP_OP_SET_HOSTNAME, +}; + +typedef struct +{ + uint32_t op; + uint32_t flags; + uint32_t perms; + size_t size_arg; + uint32_t arg1_offset; + uint32_t arg2_offset; +} PrivSepOp; + +/* + * DEFINE_LINKED_LIST: + * @Type: A struct with a `Type *next` member + * @name: Used to form the names of variables and functions + * + * Define a global linked list of @Type structures, with pointers + * `NAMEs` to the head of the list and `last_NAME` to the tail of the + * list. + * + * A new zero-filled item can be allocated and appended to the list + * by calling `_NAME_append_new()`, which returns the new item. + */ +#define DEFINE_LINKED_LIST(Type, name) \ +static Type *name ## s = NULL; \ +static Type *last_ ## name = NULL; \ +\ +static inline Type * \ +_ ## name ## _append_new (void) \ +{ \ + Type *self = xcalloc (1, sizeof (Type)); \ +\ + if (last_ ## name != NULL) \ + last_ ## name ->next = self; \ + else \ + name ## s = self; \ +\ + last_ ## name = self; \ + return self; \ +} + +DEFINE_LINKED_LIST (SetupOp, op) + +static SetupOp * +setup_op_new (SetupOpType type) +{ + SetupOp *op = _op_append_new (); + + op->type = type; + op->fd = -1; + op->flags = 0; + return op; +} + +DEFINE_LINKED_LIST (LockFile, lock_file) + +static LockFile * +lock_file_new (const char *path) +{ + LockFile *lock = _lock_file_append_new (); + + lock->path = path; + return lock; +} + +typedef struct _SeccompProgram SeccompProgram; + +struct _SeccompProgram +{ + struct sock_fprog program; + SeccompProgram *next; +}; + +DEFINE_LINKED_LIST (SeccompProgram, seccomp_program) + +static SeccompProgram * +seccomp_program_new (int *fd) +{ + SeccompProgram *self = _seccomp_program_append_new (); + cleanup_free char *data = NULL; + size_t len; + + data = load_file_data (*fd, &len); + + if (data == NULL) + die_with_error ("Can't read seccomp data"); + + close (*fd); + *fd = -1; + + if (len % 8 != 0) + die ("Invalid seccomp data, must be multiple of 8"); + + self->program.len = len / 8; + self->program.filter = (struct sock_filter *) steal_pointer (&data); + return self; +} + +static void +seccomp_programs_apply (void) +{ + SeccompProgram *program; + + for (program = seccomp_programs; program != NULL; program = program->next) + { + if (prctl (PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program->program) != 0) + { + if (errno == EINVAL) + die ("Unable to set up system call filtering as requested: " + "prctl(PR_SET_SECCOMP) reported EINVAL. " + "(Hint: this requires a kernel configured with " + "CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER.)"); + + die_with_error ("prctl(PR_SET_SECCOMP)"); + } + } +} + +static void +usage (int ecode, FILE *out) +{ + fprintf (out, "usage: %s [OPTIONS...] [--] COMMAND [ARGS...]\n\n", argv0 ? argv0 : "bwrap"); + + fprintf (out, + " --help Print this help\n" + " --version Print version\n" + " --args FD Parse NUL-separated args from FD\n" + " --argv0 VALUE Set argv[0] to the value VALUE before running the program\n" + " --level-prefix Prepend e.g. <3> to diagnostic messages\n" + " --unshare-all Unshare every namespace we support by default\n" + " --share-net Retain the network namespace (can only combine with --unshare-all)\n" + " --unshare-user Create new user namespace (may be automatically implied if not setuid)\n" + " --unshare-user-try Create new user namespace if possible else continue by skipping it\n" + " --unshare-ipc Create new ipc namespace\n" + " --unshare-pid Create new pid namespace\n" + " --unshare-net Create new network namespace\n" + " --unshare-uts Create new uts namespace\n" + " --unshare-cgroup Create new cgroup namespace\n" + " --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it\n" + " --userns FD Use this user namespace (cannot combine with --unshare-user)\n" + " --userns2 FD After setup switch to this user namespace, only useful with --userns\n" + " --disable-userns Disable further use of user namespaces inside sandbox\n" + " --assert-userns-disabled Fail unless further use of user namespace inside sandbox is disabled\n" + " --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)\n" + " --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\n" + " --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n" + " --hostname NAME Custom hostname in the sandbox (requires --unshare-uts)\n" + " --chdir DIR Change directory to DIR\n" + " --clearenv Unset all environment variables\n" + " --setenv VAR VALUE Set an environment variable\n" + " --unsetenv VAR Unset an environment variable\n" + " --lock-file DEST Take a lock on DEST while sandbox is running\n" + " --sync-fd FD Keep this fd open while sandbox is running\n" + " --bind SRC DEST Bind mount the host path SRC on DEST\n" + " --bind-try SRC DEST Equal to --bind but ignores non-existent SRC\n" + " --dev-bind SRC DEST Bind mount the host path SRC on DEST, allowing device access\n" + " --dev-bind-try SRC DEST Equal to --dev-bind but ignores non-existent SRC\n" + " --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n" + " --ro-bind-try SRC DEST Equal to --ro-bind but ignores non-existent SRC\n" + " --bind-fd FD DEST Bind open directory or path fd on DEST\n" + " --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST\n" + " --remount-ro DEST Remount DEST as readonly; does not recursively remount\n" + " --overlay-src SRC Read files from SRC in the following overlay\n" + " --overlay RWSRC WORKDIR DEST Mount overlayfs on DEST, with RWSRC as the host path for writes and\n" + " WORKDIR an empty directory on the same filesystem as RWSRC\n" + " --tmp-overlay DEST Mount overlayfs on DEST, with writes going to an invisible tmpfs\n" + " --ro-overlay DEST Mount overlayfs read-only on DEST\n" + " --exec-label LABEL Exec label for the sandbox\n" + " --file-label LABEL File label for temporary sandbox content\n" + " --proc DEST Mount new procfs on DEST\n" + " --dev DEST Mount new dev on DEST\n" + " --tmpfs DEST Mount new tmpfs on DEST\n" + " --mqueue DEST Mount new mqueue on DEST\n" + " --dir DEST Create dir at DEST\n" + " --file FD DEST Copy from FD to destination DEST\n" + " --bind-data FD DEST Copy from FD to file which is bind-mounted on DEST\n" + " --ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST\n" + " --symlink SRC DEST Create symlink at DEST with target SRC\n" + " --seccomp FD Load and use seccomp rules from FD (not repeatable)\n" + " --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)\n" + " --block-fd FD Block on FD until some data to read is available\n" + " --userns-block-fd FD Block on FD until the user namespace is ready\n" + " --info-fd FD Write information about the running container to FD\n" + " --json-status-fd FD Write container status to FD as multiple JSON documents\n" + " --new-session Create a new terminal session\n" + " --die-with-parent Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.\n" + " --as-pid-1 Do not install a reaper process with PID=1\n" + " --cap-add CAP Add cap CAP when running as privileged user\n" + " --cap-drop CAP Drop cap CAP when running as privileged user\n" + " --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n" + " --size BYTES Set size of next argument (only for --tmpfs)\n" + " --chmod OCTAL PATH Change permissions of PATH (must already exist)\n" + ); + exit (ecode); +} + +/* If --die-with-parent was specified, use PDEATHSIG to ensure SIGKILL + * is sent to the current process when our parent dies. + */ +static void +handle_die_with_parent (void) +{ + if (opt_die_with_parent && prctl (PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0) + die_with_error ("prctl"); +} + +static void +block_sigchild (void) +{ + sigset_t mask; + int status; + + sigemptyset (&mask); + sigaddset (&mask, SIGCHLD); + + if (sigprocmask (SIG_BLOCK, &mask, NULL) == -1) + die_with_error ("sigprocmask"); + + /* Reap any outstanding zombies that we may have inherited */ + while (waitpid (-1, &status, WNOHANG) > 0) + ; +} + +static void +unblock_sigchild (void) +{ + sigset_t mask; + + sigemptyset (&mask); + sigaddset (&mask, SIGCHLD); + + if (sigprocmask (SIG_UNBLOCK, &mask, NULL) == -1) + die_with_error ("sigprocmask"); +} + +/* Closes all fd:s except 0,1,2 and the passed in array of extra fds */ +static int +close_extra_fds (void *data, int fd) +{ + int *extra_fds = (int *) data; + int i; + + for (i = 0; extra_fds[i] != -1; i++) + if (fd == extra_fds[i]) + return 0; + + if (fd <= 2) + return 0; + + close (fd); + return 0; +} + +static int +propagate_exit_status (int status) +{ + if (WIFEXITED (status)) + return WEXITSTATUS (status); + + /* The process died of a signal, we can't really report that, but we + * can at least be bash-compatible. The bash manpage says: + * The return value of a simple command is its + * exit status, or 128+n if the command is + * terminated by signal n. + */ + if (WIFSIGNALED (status)) + return 128 + WTERMSIG (status); + + /* Weird? */ + return 255; +} + +static void +dump_info (int fd, const char *output, bool exit_on_error) +{ + size_t len = strlen (output); + if (write_to_fd (fd, output, len)) + { + if (exit_on_error) + die_with_error ("Write to info_fd"); + } +} + +static void +report_child_exit_status (int exitc, int setup_finished_fd) +{ + ssize_t s; + char data[2]; + cleanup_free char *output = NULL; + if (opt_json_status_fd == -1 || setup_finished_fd == -1) + return; + + s = TEMP_FAILURE_RETRY (read (setup_finished_fd, data, sizeof data)); + if (s == -1 && errno != EAGAIN) + die_with_error ("read eventfd"); + if (s != 1) // Is 0 if pipe closed before exec, is 2 if closed after exec. + return; + + output = xasprintf ("{ \"exit-code\": %i }\n", exitc); + dump_info (opt_json_status_fd, output, false); + close (opt_json_status_fd); + opt_json_status_fd = -1; + close (setup_finished_fd); +} + +/* This stays around for as long as the initial process in the app does + * and when that exits it exits, propagating the exit status. We do this + * by having pid 1 in the sandbox detect this exit and tell the monitor + * the exit status via a eventfd. We also track the exit of the sandbox + * pid 1 via a signalfd for SIGCHLD, and exit with an error in this case. + * This is to catch e.g. problems during setup. */ +static int +monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd) +{ + int res; + uint64_t val; + ssize_t s; + int signal_fd; + sigset_t mask; + struct pollfd fds[2]; + int num_fds; + struct signalfd_siginfo fdsi; + int dont_close[] = {-1, -1, -1, -1}; + unsigned int j = 0; + int exitc; + pid_t died_pid; + int died_status; + + /* Close all extra fds in the monitoring process. + Any passed in fds have been passed on to the child anyway. */ + if (event_fd != -1) + dont_close[j++] = event_fd; + if (opt_json_status_fd != -1) + dont_close[j++] = opt_json_status_fd; + if (setup_finished_fd != -1) + dont_close[j++] = setup_finished_fd; + assert (j < sizeof(dont_close)/sizeof(*dont_close)); + fdwalk (proc_fd, close_extra_fds, dont_close); + + sigemptyset (&mask); + sigaddset (&mask, SIGCHLD); + + signal_fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); + if (signal_fd == -1) + die_with_error ("Can't create signalfd"); + + num_fds = 1; + fds[0].fd = signal_fd; + fds[0].events = POLLIN; + if (event_fd != -1) + { + fds[1].fd = event_fd; + fds[1].events = POLLIN; + num_fds++; + } + + while (1) + { + fds[0].revents = fds[1].revents = 0; + res = poll (fds, num_fds, -1); + if (res == -1 && errno != EINTR) + die_with_error ("poll"); + + /* Always read from the eventfd first, if pid 2 died then pid 1 often + * dies too, and we could race, reporting that first and we'd lose + * the real exit status. */ + if (event_fd != -1) + { + s = read (event_fd, &val, 8); + if (s == -1 && errno != EINTR && errno != EAGAIN) + die_with_error ("read eventfd"); + else if (s == 8) + { + exitc = (int) val - 1; + report_child_exit_status (exitc, setup_finished_fd); + return exitc; + } + } + + /* We need to read the signal_fd, or it will keep polling as read, + * however we ignore the details as we get them from waitpid + * below anyway */ + s = read (signal_fd, &fdsi, sizeof (struct signalfd_siginfo)); + if (s == -1 && errno != EINTR && errno != EAGAIN) + die_with_error ("read signalfd"); + + /* We may actually get several sigchld compressed into one + SIGCHLD, so we have to handle all of them. */ + while ((died_pid = waitpid (-1, &died_status, WNOHANG)) > 0) + { + /* We may be getting sigchild from other children too. For instance if + someone created a child process, and then exec:ed bubblewrap. Ignore them */ + if (died_pid == child_pid) + { + exitc = propagate_exit_status (died_status); + report_child_exit_status (exitc, setup_finished_fd); + return exitc; + } + } + } + + die ("Should not be reached"); + + return 0; +} + +/* This is pid 1 in the app sandbox. It is needed because we're using + * pid namespaces, and someone has to reap zombies in it. We also detect + * when the initial process (pid 2) dies and report its exit status to + * the monitor so that it can return it to the original spawner. + * + * When there are no other processes in the sandbox the wait will return + * ECHILD, and we then exit pid 1 to clean up the sandbox. */ +static int +do_init (int event_fd, pid_t initial_pid) +{ + int initial_exit_status = 1; + LockFile *lock; + + for (lock = lock_files; lock != NULL; lock = lock->next) + { + int fd = TEMP_FAILURE_RETRY (open (lock->path, O_RDONLY | O_CLOEXEC)); + if (fd == -1) + die_with_error ("Unable to open lock file %s", lock->path); + + struct flock l = { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + + if (TEMP_FAILURE_RETRY (fcntl (fd, F_SETLK, &l)) < 0) + die_with_error ("Unable to lock file %s", lock->path); + + /* Keep fd open to hang on to lock */ + lock->fd = fd; + } + + /* Optionally bind our lifecycle to that of the caller */ + handle_die_with_parent (); + + seccomp_programs_apply (); + + while (true) + { + pid_t child; + int status; + + child = TEMP_FAILURE_RETRY (wait (&status)); + if (child == initial_pid) + { + initial_exit_status = propagate_exit_status (status); + + if(event_fd != -1) + { + uint64_t val; + int res UNUSED; + + val = initial_exit_status + 1; + res = TEMP_FAILURE_RETRY (write (event_fd, &val, 8)); + /* Ignore res, if e.g. the parent died and closed event_fd + we don't want to error out here */ + } + } + + if (child == -1 && errno != EINTR) + { + if (errno != ECHILD) + die_with_error ("init wait()"); + break; + } + } + + /* Close FDs. */ + for (lock = lock_files; lock != NULL; lock = lock->next) + { + if (lock->fd >= 0) + { + close (lock->fd); + lock->fd = -1; + } + } + + return initial_exit_status; +} + +#define CAP_TO_MASK_0(x) (1L << ((x) & 31)) +#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) + +/* Set if --cap-add or --cap-drop were used */ +static bool opt_cap_add_or_drop_used; +/* The capability set we'll target, used if above is true */ +static uint32_t requested_caps[2] = {0, 0}; + +/* low 32bit caps needed */ +/* CAP_SYS_PTRACE is needed to dereference the symlinks in /proc//ns/, see namespaces(7) */ +#define REQUIRED_CAPS_0 (CAP_TO_MASK_0 (CAP_SYS_ADMIN) | CAP_TO_MASK_0 (CAP_SYS_CHROOT) | CAP_TO_MASK_0 (CAP_NET_ADMIN) | CAP_TO_MASK_0 (CAP_SETUID) | CAP_TO_MASK_0 (CAP_SETGID) | CAP_TO_MASK_0 (CAP_SYS_PTRACE)) +/* high 32bit caps needed */ +#define REQUIRED_CAPS_1 0 + +static void +set_required_caps (void) +{ + struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct data[2] = { { 0 } }; + + /* Drop all non-require capabilities */ + data[0].effective = REQUIRED_CAPS_0; + data[0].permitted = REQUIRED_CAPS_0; + data[0].inheritable = 0; + data[1].effective = REQUIRED_CAPS_1; + data[1].permitted = REQUIRED_CAPS_1; + data[1].inheritable = 0; + if (capset (&hdr, data) < 0) + die_with_error ("capset failed"); +} + +static void +drop_all_caps (bool keep_requested_caps) +{ + struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct data[2] = { { 0 } }; + + if (keep_requested_caps) + { + /* Avoid calling capset() unless we need to; currently + * systemd-nspawn at least is known to install a seccomp + * policy denying capset() for dubious reasons. + * + */ + if (!opt_cap_add_or_drop_used && real_uid == 0) + { + assert (!is_privileged); + return; + } + data[0].effective = requested_caps[0]; + data[0].permitted = requested_caps[0]; + data[0].inheritable = requested_caps[0]; + data[1].effective = requested_caps[1]; + data[1].permitted = requested_caps[1]; + data[1].inheritable = requested_caps[1]; + } + + if (capset (&hdr, data) < 0) + { + /* While the above logic ensures we don't call capset() for the primary + * process unless configured to do so, we still try to drop privileges for + * the init process unconditionally. Since due to the systemd seccomp + * filter that will fail, let's just ignore it. + */ + if (errno == EPERM && real_uid == 0 && !is_privileged) + return; + else + die_with_error ("capset failed"); + } +} + +static bool +has_caps (void) +{ + struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct data[2] = { { 0 } }; + + if (capget (&hdr, data) < 0) + die_with_error ("capget failed"); + + return data[0].permitted != 0 || data[1].permitted != 0; +} + +/* Most of the code here is used both to add caps to the ambient capabilities + * and drop caps from the bounding set. Handle both cases here and add + * drop_cap_bounding_set/set_ambient_capabilities wrappers to facilitate its usage. + */ +static void +prctl_caps (uint32_t *caps, bool do_cap_bounding, bool do_set_ambient) +{ + unsigned long cap; + + /* We ignore both EINVAL and EPERM, as we are actually relying + * on PR_SET_NO_NEW_PRIVS to ensure the right capabilities are + * available. EPERM in particular can happen with old, buggy + * kernels. See: + * https://github.com/projectatomic/bubblewrap/pull/175#issuecomment-278051373 + * https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/security/commoncap.c?id=160da84dbb39443fdade7151bc63a88f8e953077 + */ + for (cap = 0; cap <= CAP_LAST_CAP; cap++) + { + bool keep = false; + if (cap < 32) + { + if (CAP_TO_MASK_0 (cap) & caps[0]) + keep = true; + } + else + { + if (CAP_TO_MASK_1 (cap) & caps[1]) + keep = true; + } + + if (keep && do_set_ambient) + { +#ifdef PR_CAP_AMBIENT + int res = prctl (PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0); + if (res == -1 && !(errno == EINVAL || errno == EPERM)) + die_with_error ("Adding ambient capability %ld", cap); +#else + /* We ignore the EINVAL that results from not having PR_CAP_AMBIENT + * in the current kernel at runtime, so also ignore not having it + * in the current kernel headers at compile-time */ +#endif + } + + if (!keep && do_cap_bounding) + { + int res = prctl (PR_CAPBSET_DROP, cap, 0, 0, 0); + if (res == -1 && !(errno == EINVAL || errno == EPERM)) + die_with_error ("Dropping capability %ld from bounds", cap); + } + } +} + +static void +drop_cap_bounding_set (bool drop_all) +{ + if (!drop_all) + prctl_caps (requested_caps, true, false); + else + { + uint32_t no_caps[2] = {0, 0}; + prctl_caps (no_caps, true, false); + } +} + +static void +set_ambient_capabilities (void) +{ + if (is_privileged) + return; + prctl_caps (requested_caps, false, true); +} + +/* This acquires the privileges that the bwrap will need it to work. + * If bwrap is not setuid, then this does nothing, and it relies on + * unprivileged user namespaces to be used. This case is + * "is_privileged = false". + * + * If bwrap is setuid, then we do things in phases. + * The first part is run as euid 0, but with fsuid as the real user. + * The second part, inside the child, is run as the real user but with + * capabilities. + * And finally we drop all capabilities. + * The reason for the above dance is to avoid having the setup phase + * being able to read files the user can't, while at the same time + * working around various kernel issues. See below for details. + */ +static void +acquire_privs (void) +{ + uid_t euid, new_fsuid; + + euid = geteuid (); + + /* Are we setuid ? */ + if (real_uid != euid) + { + if (euid != 0) + die ("Unexpected setuid user %d, should be 0", euid); + + is_privileged = true; + /* We want to keep running as euid=0 until at the clone() + * operation because doing so will make the user namespace be + * owned by root, which makes it not ptrace:able by the user as + * it otherwise would be. After that we will run fully as the + * user, which is necessary e.g. to be able to read from a fuse + * mount from the user. + * + * However, we don't want to accidentally mis-use euid=0 for + * escalated filesystem access before the clone(), so we set + * fsuid to the uid. + */ + if (setfsuid (real_uid) < 0) + die_with_error ("Unable to set fsuid"); + + /* setfsuid can't properly report errors, check that it worked (as per manpage) */ + new_fsuid = setfsuid (-1); + if (new_fsuid != real_uid) + die ("Unable to set fsuid (was %d)", (int)new_fsuid); + + /* We never need capabilities after execve(), so lets drop everything from the bounding set */ + drop_cap_bounding_set (true); + + /* Keep only the required capabilities for setup */ + set_required_caps (); + } + else if (real_uid != 0 && has_caps ()) + { + /* We have some capabilities in the non-setuid case, which should not happen. + Probably caused by the binary being setcap instead of setuid which we + don't support anymore */ + die ("Unexpected capabilities but not setuid, old file caps config?"); + } + else if (real_uid == 0) + { + /* If our uid is 0, default to inheriting all caps; the caller + * can drop them via --cap-drop. This is used by at least rpm-ostree. + * Note this needs to happen before the argument parsing of --cap-drop. + */ + struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 }; + struct __user_cap_data_struct data[2] = { { 0 } }; + + if (capget (&hdr, data) < 0) + die_with_error ("capget (for uid == 0) failed"); + + requested_caps[0] = data[0].effective; + requested_caps[1] = data[1].effective; + } + + /* Else, we try unprivileged user namespaces */ +} + +/* This is called once we're inside the namespace */ +static void +switch_to_user_with_privs (void) +{ + /* If we're in a new user namespace, we got back the bounding set, clear it again */ + if (opt_unshare_user || opt_userns_fd != -1) + drop_cap_bounding_set (false); + + /* If we switched to a new user namespace it may allow other uids/gids, so switch to the target one */ + if (opt_userns_fd != -1) + { + if (opt_sandbox_uid != real_uid && setuid (opt_sandbox_uid) < 0) + die_with_error ("unable to switch to uid %d", opt_sandbox_uid); + + if (opt_sandbox_gid != real_gid && setgid (opt_sandbox_gid) < 0) + die_with_error ("unable to switch to gid %d", opt_sandbox_gid); + } + + if (!is_privileged) + return; + + /* Tell kernel not clear capabilities when later dropping root uid */ + if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) + die_with_error ("prctl(PR_SET_KEEPCAPS) failed"); + + if (setuid (opt_sandbox_uid) < 0) + die_with_error ("unable to drop root uid"); + + /* Regain effective required capabilities from permitted */ + set_required_caps (); +} + +/* Call setuid() and use capset() to adjust capabilities */ +static void +drop_privs (bool keep_requested_caps, + bool already_changed_uid) +{ + assert (!keep_requested_caps || !is_privileged); + /* Drop root uid */ + if (is_privileged && !already_changed_uid && + setuid (opt_sandbox_uid) < 0) + die_with_error ("unable to drop root uid"); + + drop_all_caps (keep_requested_caps); + + /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */ + if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0) + die_with_error ("can't set dumpable"); +} + +static void +write_uid_gid_map (uid_t sandbox_uid, + uid_t parent_uid, + uid_t sandbox_gid, + uid_t parent_gid, + pid_t pid, + bool deny_groups, + bool map_root) +{ + cleanup_free char *uid_map = NULL; + cleanup_free char *gid_map = NULL; + cleanup_free char *dir = NULL; + cleanup_fd int dir_fd = -1; + uid_t old_fsuid = (uid_t)-1; + + if (pid == -1) + dir = xstrdup ("self"); + else + dir = xasprintf ("%d", pid); + + dir_fd = openat (proc_fd, dir, O_PATH); + if (dir_fd < 0) + die_with_error ("open /proc/%s failed", dir); + + if (map_root && parent_uid != 0 && sandbox_uid != 0) + uid_map = xasprintf ("0 %d 1\n" + "%d %d 1\n", overflow_uid, sandbox_uid, parent_uid); + else + uid_map = xasprintf ("%d %d 1\n", sandbox_uid, parent_uid); + + if (map_root && parent_gid != 0 && sandbox_gid != 0) + gid_map = xasprintf ("0 %d 1\n" + "%d %d 1\n", overflow_gid, sandbox_gid, parent_gid); + else + gid_map = xasprintf ("%d %d 1\n", sandbox_gid, parent_gid); + + /* We have to be root to be allowed to write to the uid map + * for setuid apps, so temporary set fsuid to 0 */ + if (is_privileged) + old_fsuid = setfsuid (0); + + if (write_file_at (dir_fd, "uid_map", uid_map) != 0) + die_with_error ("setting up uid map"); + + if (deny_groups && + write_file_at (dir_fd, "setgroups", "deny\n") != 0) + { + /* If /proc/[pid]/setgroups does not exist, assume we are + * running a linux kernel < 3.19, i.e. we live with the + * vulnerability known as CVE-2014-8989 in older kernels + * where setgroups does not exist. + */ + if (errno != ENOENT) + die_with_error ("error writing to setgroups"); + } + + if (write_file_at (dir_fd, "gid_map", gid_map) != 0) + die_with_error ("setting up gid map"); + + if (is_privileged) + { + setfsuid (old_fsuid); + if ((uid_t) setfsuid (-1) != real_uid) + die ("Unable to re-set fsuid"); + } +} + +static void +privileged_op (int privileged_op_socket, + uint32_t op, + uint32_t flags, + uint32_t perms, + size_t size_arg, + const char *arg1, + const char *arg2) +{ + bind_mount_result bind_result; + char *failing_path = NULL; + + if (privileged_op_socket != -1) + { + uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */ + PrivSepOp *op_buffer = (PrivSepOp *) buffer; + size_t buffer_size = sizeof (PrivSepOp); + uint32_t arg1_offset = 0, arg2_offset = 0; + + /* We're unprivileged, send this request to the privileged part */ + + if (arg1 != NULL) + { + arg1_offset = buffer_size; + buffer_size += strlen (arg1) + 1; + } + if (arg2 != NULL) + { + arg2_offset = buffer_size; + buffer_size += strlen (arg2) + 1; + } + + if (buffer_size >= sizeof (buffer)) + die ("privilege separation operation to large"); + + op_buffer->op = op; + op_buffer->flags = flags; + op_buffer->perms = perms; + op_buffer->size_arg = size_arg; + op_buffer->arg1_offset = arg1_offset; + op_buffer->arg2_offset = arg2_offset; + if (arg1 != NULL) + strcpy ((char *) buffer + arg1_offset, arg1); + if (arg2 != NULL) + strcpy ((char *) buffer + arg2_offset, arg2); + + if (TEMP_FAILURE_RETRY (write (privileged_op_socket, buffer, buffer_size)) != (ssize_t)buffer_size) + die ("Can't write to privileged_op_socket"); + + if (TEMP_FAILURE_RETRY (read (privileged_op_socket, buffer, 1)) != 1) + die ("Can't read from privileged_op_socket"); + + return; + } + + /* + * This runs a privileged request for the unprivileged setup + * code. Note that since the setup code is unprivileged it is not as + * trusted, so we need to verify that all requests only affect the + * child namespace as set up by the privileged parts of the setup, + * and that all the code is very careful about handling input. + * + * This means: + * * Bind mounts are safe, since we always use filesystem namespace. They + * must be recursive though, as otherwise you can use a non-recursive bind + * mount to access an otherwise over-mounted mountpoint. + * * Mounting proc, tmpfs, mqueue, devpts in the child namespace is assumed to + * be safe. + * * Remounting RO (even non-recursive) is safe because it decreases privileges. + * * sethostname() is safe only if we set up a UTS namespace + */ + switch (op) + { + case PRIV_SEP_OP_DONE: + break; + + case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE: + bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY, &failing_path); + + if (bind_result != BIND_MOUNT_SUCCESS) + die_with_bind_result (bind_result, errno, failing_path, + "Can't remount readonly on %s", arg2); + + assert (failing_path == NULL); /* otherwise we would have died */ + break; + + case PRIV_SEP_OP_BIND_MOUNT: + /* We always bind directories recursively, otherwise this would let us + access files that are otherwise covered on the host */ + bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags, &failing_path); + + if (bind_result != BIND_MOUNT_SUCCESS) + die_with_bind_result (bind_result, errno, failing_path, + "Can't bind mount %s on %s", arg1, arg2); + + assert (failing_path == NULL); /* otherwise we would have died */ + break; + + case PRIV_SEP_OP_PROC_MOUNT: + if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) + die_with_mount_error ("Can't mount proc on %s", arg1); + break; + + case PRIV_SEP_OP_TMPFS_MOUNT: + { + cleanup_free char *mode = NULL; + + /* This check should be unnecessary since we checked this when parsing + * the --size option as well. However, better be safe than sorry. */ + if (size_arg > MAX_TMPFS_BYTES) + die_with_error ("Specified tmpfs size too large (%zu > %zu)", size_arg, MAX_TMPFS_BYTES); + + if (size_arg != 0) + mode = xasprintf ("mode=%#o,size=%zu", perms, size_arg); + else + mode = xasprintf ("mode=%#o", perms); + + cleanup_free char *opt = label_mount (mode, opt_file_label); + if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0) + die_with_mount_error ("Can't mount tmpfs on %s", arg1); + break; + } + + case PRIV_SEP_OP_DEVPTS_MOUNT: + if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC, + "newinstance,ptmxmode=0666,mode=620") != 0) + die_with_mount_error ("Can't mount devpts on %s", arg1); + break; + + case PRIV_SEP_OP_MQUEUE_MOUNT: + if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0) + die_with_mount_error ("Can't mount mqueue on %s", arg1); + break; + + case PRIV_SEP_OP_OVERLAY_MOUNT: + if (mount ("overlay", arg2, "overlay", MS_MGC_VAL, arg1) != 0) + { + /* The standard message for ELOOP, "Too many levels of symbolic + * links", is not helpful here. */ + if (errno == ELOOP) + die ("Can't make overlay mount on %s with options %s: " + "Overlay directories may not overlap", + arg2, arg1); + die_with_mount_error ("Can't make overlay mount on %s with options %s", + arg2, arg1); + } + break; + + case PRIV_SEP_OP_SET_HOSTNAME: + /* This is checked at the start, but lets verify it here in case + something manages to send hacked priv-sep operation requests. */ + if (!opt_unshare_uts) + die ("Refusing to set hostname in original namespace"); + if (sethostname (arg1, strlen(arg1)) != 0) + die_with_error ("Can't set hostname to %s", arg1); + break; + + default: + die ("Unexpected privileged op %d", op); + } +} + +/* This is run unprivileged in the child namespace but can request + * some privileged operations (also in the child namespace) via the + * privileged_op_socket. + */ +static void +setup_newroot (bool unshare_pid, + int privileged_op_socket) +{ + SetupOp *op; + int tmp_overlay_idx = 0; + + for (op = ops; op != NULL; op = op->next) + { + cleanup_free char *source = NULL; + cleanup_free char *dest = NULL; + int source_mode = 0; + unsigned int i; + + if (op->source && + op->type != SETUP_MAKE_SYMLINK) + { + source = get_oldroot_path (op->source); + source_mode = get_file_mode (source); + if (source_mode < 0) + { + if (op->flags & ALLOW_NOTEXIST && errno == ENOENT) + continue; /* Ignore and move on */ + die_with_error("Can't get type of source %s", op->source); + } + } + + if (op->dest && + (op->flags & NO_CREATE_DEST) == 0) + { + unsigned parent_mode = 0755; + + /* If we're creating a file that is inaccessible by the owning group, + * try to achieve least-astonishment by creating parents also + * inaccessible by that group. */ + if (op->perms >= 0 && + (op->perms & 0070) == 0) + parent_mode &= ~0050U; + + /* The same, but for users other than the owner and group. */ + if (op->perms >= 0 && + (op->perms & 0007) == 0) + parent_mode &= ~0005U; + + dest = get_newroot_path (op->dest); + if (mkdir_with_parents (dest, parent_mode, false) != 0) + die_with_error ("Can't mkdir parents for %s", op->dest); + } + + switch (op->type) + { + case SETUP_RO_BIND_MOUNT: + case SETUP_DEV_BIND_MOUNT: + case SETUP_BIND_MOUNT: + if (source_mode == S_IFDIR) + { + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + } + else if (ensure_file (dest, 0444) != 0) + die_with_error ("Can't create file at %s", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, + (op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) | + (op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0), + 0, 0, source, dest); + + if (op->fd >= 0) + { + struct stat fd_st, mount_st; + + /* When using bind-fd, there is a race condition between resolving the fd as a magic symlink + * and mounting it, where someone could replace what is at the symlink target. Ideally + * we would not even resolve the symlink and directly bind-mount from the fd, but unfortunately + * we can't do that, because its not permitted to bind mount a fd from another user namespace. + * So, we resolve, mount and then compare fstat+stat to detect the race. */ + + if (fstat(op->fd, &fd_st) != 0) + die_with_error("Can't stat fd %d", op->fd); + if (lstat(dest, &mount_st) != 0) + die_with_error("Can't stat mount at %s", dest); + + if (fd_st.st_ino != mount_st.st_ino || + fd_st.st_dev != mount_st.st_dev) + die_with_error("Race condition binding dirfd"); + + close(op->fd); + op->fd = -1; + } + + break; + + case SETUP_OVERLAY_MOUNT: + case SETUP_RO_OVERLAY_MOUNT: + case SETUP_TMP_OVERLAY_MOUNT: + { + StringBuilder sb = {0}; + bool multi_src = false; + + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + if (op->source != NULL) + { + strappend (&sb, "upperdir=/oldroot"); + strappend_escape_for_mount_options (&sb, op->source); + strappend (&sb, ",workdir=/oldroot"); + op = op->next; + strappend_escape_for_mount_options (&sb, op->source); + strappend (&sb, ","); + } + else if (op->type == SETUP_TMP_OVERLAY_MOUNT) + strappendf (&sb, "upperdir=/tmp-overlay-upper-%1$d,workdir=/tmp-overlay-work-%1$d,", + tmp_overlay_idx++); + + strappend (&sb, "lowerdir=/oldroot"); + while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC) + { + op = op->next; + if (multi_src) + strappend (&sb, ":/oldroot"); + strappend_escape_for_mount_options (&sb, op->source); + multi_src = true; + } + + strappend (&sb, ",userxattr"); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_OVERLAY_MOUNT, 0, 0, 0, sb.str, dest); + free (sb.str); + } + break; + + case SETUP_REMOUNT_RO_NO_RECURSIVE: + privileged_op (privileged_op_socket, + PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, 0, NULL, dest); + break; + + case SETUP_MOUNT_PROC: + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + if (unshare_pid || opt_pidns_fd != -1) + { + /* Our own procfs */ + privileged_op (privileged_op_socket, + PRIV_SEP_OP_PROC_MOUNT, 0, 0, 0, + dest, NULL); + } + else + { + /* Use system procfs, as we share pid namespace anyway */ + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, 0, 0, 0, + "oldroot/proc", dest); + } + + /* There are a bunch of weird old subdirs of /proc that could potentially be + problematic (for instance /proc/sysrq-trigger lets you shut down the machine + if you have write access). We should not have access to these as a non-privileged + user, but lets cover them anyway just to make sure */ + static const char * const cover_proc_dirs[] = { "sys", "sysrq-trigger", "irq", "bus" }; + for (i = 0; i < N_ELEMENTS (cover_proc_dirs); i++) + { + cleanup_free char *subdir = strconcat3 (dest, "/", cover_proc_dirs[i]); + if (access (subdir, W_OK) < 0) + { + /* The file is already read-only or doesn't exist. */ + if (errno == EACCES || errno == ENOENT || errno == EROFS) + continue; + + die_with_error ("Can't access %s", subdir); + } + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, BIND_READONLY, 0, 0, + subdir, subdir); + } + + break; + + case SETUP_MOUNT_DEV: + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_TMPFS_MOUNT, 0, 0755, 0, + dest, NULL); + + static const char *const devnodes[] = { "null", "zero", "full", "random", "urandom", "tty" }; + for (i = 0; i < N_ELEMENTS (devnodes); i++) + { + cleanup_free char *node_dest = strconcat3 (dest, "/", devnodes[i]); + cleanup_free char *node_src = strconcat ("/oldroot/dev/", devnodes[i]); + if (create_file (node_dest, 0444, NULL) != 0) + die_with_error ("Can't create file %s/%s", op->dest, devnodes[i]); + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0, + node_src, node_dest); + } + + static const char *const stdionodes[] = { "stdin", "stdout", "stderr" }; + for (i = 0; i < N_ELEMENTS (stdionodes); i++) + { + cleanup_free char *target = xasprintf ("/proc/self/fd/%d", i); + cleanup_free char *node_dest = strconcat3 (dest, "/", stdionodes[i]); + if (symlink (target, node_dest) < 0) + die_with_error ("Can't create symlink %s/%s", op->dest, stdionodes[i]); + } + + /* /dev/fd and /dev/core - legacy, but both nspawn and docker do these */ + { cleanup_free char *dev_fd = strconcat (dest, "/fd"); + if (symlink ("/proc/self/fd", dev_fd) < 0) + die_with_error ("Can't create symlink %s", dev_fd); + } + { cleanup_free char *dev_core = strconcat (dest, "/core"); + if (symlink ("/proc/kcore", dev_core) < 0) + die_with_error ("Can't create symlink %s", dev_core); + } + + { + cleanup_free char *pts = strconcat (dest, "/pts"); + cleanup_free char *ptmx = strconcat (dest, "/ptmx"); + cleanup_free char *shm = strconcat (dest, "/shm"); + + if (mkdir (shm, 0755) == -1) + die_with_error ("Can't create %s/shm", op->dest); + + if (mkdir (pts, 0755) == -1) + die_with_error ("Can't create %s/devpts", op->dest); + privileged_op (privileged_op_socket, + PRIV_SEP_OP_DEVPTS_MOUNT, 0, 0, 0, pts, NULL); + + if (symlink ("pts/ptmx", ptmx) != 0) + die_with_error ("Can't make symlink at %s/ptmx", op->dest); + } + + /* If stdout is a tty, that means the sandbox can write to the + outside-sandbox tty. In that case we also create a /dev/console + that points to this tty device. This should not cause any more + access than we already have, and it makes ttyname() work in the + sandbox. */ + if (host_tty_dev != NULL && *host_tty_dev != 0) + { + cleanup_free char *src_tty_dev = strconcat ("/oldroot", host_tty_dev); + cleanup_free char *dest_console = strconcat (dest, "/console"); + + if (create_file (dest_console, 0444, NULL) != 0) + die_with_error ("creating %s/console", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, BIND_DEVICES, 0, 0, + src_tty_dev, dest_console); + } + + break; + + case SETUP_MOUNT_TMPFS: + assert (dest != NULL); + assert (op->perms >= 0); + assert (op->perms <= 07777); + + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_TMPFS_MOUNT, 0, op->perms, op->size, + dest, NULL); + break; + + case SETUP_MOUNT_MQUEUE: + if (ensure_dir (dest, 0755) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_MQUEUE_MOUNT, 0, 0, 0, + dest, NULL); + break; + + case SETUP_MAKE_DIR: + assert (dest != NULL); + assert (op->perms >= 0); + assert (op->perms <= 07777); + + if (ensure_dir (dest, op->perms) != 0) + die_with_error ("Can't mkdir %s", op->dest); + + break; + + case SETUP_CHMOD: + assert (op->dest != NULL); + /* We used NO_CREATE_DEST so we have to use get_newroot_path() + * explicitly */ + assert (dest == NULL); + dest = get_newroot_path (op->dest); + assert (dest != NULL); + assert (op->perms >= 0); + assert (op->perms <= 07777); + + if (chmod (dest, op->perms) != 0) + die_with_error ("Can't chmod %#o %s", op->perms, op->dest); + + break; + + case SETUP_MAKE_FILE: + { + cleanup_fd int dest_fd = -1; + + assert (dest != NULL); + assert (op->perms >= 0); + assert (op->perms <= 07777); + + dest_fd = creat (dest, op->perms); + if (dest_fd == -1) + die_with_error ("Can't create file %s", op->dest); + + if (copy_file_data (op->fd, dest_fd) != 0) + die_with_error ("Can't write data to file %s", op->dest); + + close (op->fd); + op->fd = -1; + } + break; + + case SETUP_MAKE_BIND_FILE: + case SETUP_MAKE_RO_BIND_FILE: + { + cleanup_fd int dest_fd = -1; + char tempfile[] = "/bindfileXXXXXX"; + + assert (dest != NULL); + assert (op->perms >= 0); + assert (op->perms <= 07777); + + dest_fd = mkstemp (tempfile); + if (dest_fd == -1) + die_with_error ("Can't create tmpfile for %s", op->dest); + + if (fchmod (dest_fd, op->perms) != 0) + die_with_error ("Can't set mode %#o on file to be used for %s", + op->perms, op->dest); + + if (copy_file_data (op->fd, dest_fd) != 0) + die_with_error ("Can't write data to file %s", op->dest); + + close (op->fd); + op->fd = -1; + + assert (dest != NULL); + + if (ensure_file (dest, 0444) != 0) + die_with_error ("Can't create file at %s", op->dest); + + privileged_op (privileged_op_socket, + PRIV_SEP_OP_BIND_MOUNT, + (op->type == SETUP_MAKE_RO_BIND_FILE ? BIND_READONLY : 0), + 0, 0, tempfile, dest); + + /* Remove the file so we're sure the app can't get to it in any other way. + Its outside the container chroot, so it shouldn't be possible, but lets + make it really sure. */ + unlink (tempfile); + } + break; + + case SETUP_MAKE_SYMLINK: + assert (op->source != NULL); /* guaranteed by the constructor */ + if (symlink (op->source, dest) != 0) + { + if (errno == EEXIST) + { + cleanup_free char *existing = readlink_malloc (dest); + if (existing == NULL) + { + if (errno == EINVAL) + die ("Can't make symlink at %s: destination exists and is not a symlink", op->dest); + else + die_with_error ("Can't make symlink at %s: destination exists, and cannot read symlink target", op->dest); + } + + if (strcmp (existing, op->source) == 0) + break; + + die ("Can't make symlink at %s: existing destination is %s", op->dest, existing); + } + die_with_error ("Can't make symlink at %s", op->dest); + } + break; + + case SETUP_SET_HOSTNAME: + assert (op->dest != NULL); /* guaranteed by the constructor */ + privileged_op (privileged_op_socket, + PRIV_SEP_OP_SET_HOSTNAME, 0, 0, 0, + op->dest, NULL); + break; + + case SETUP_OVERLAY_SRC: /* handled by SETUP_OVERLAY_MOUNT */ + default: + die ("Unexpected type %d", op->type); + } + } + privileged_op (privileged_op_socket, + PRIV_SEP_OP_DONE, 0, 0, 0, NULL, NULL); +} + +/* Do not leak file descriptors already used by setup_newroot () */ +static void +close_ops_fd (void) +{ + SetupOp *op; + + for (op = ops; op != NULL; op = op->next) + { + if (op->fd != -1) + { + (void) close (op->fd); + op->fd = -1; + } + } +} + +/* We need to resolve relative symlinks in the sandbox before we + chroot so that absolute symlinks are handled correctly. We also + need to do this after we've switched to the real uid so that + e.g. paths on fuse mounts work */ +static void +resolve_symlinks_in_ops (void) +{ + SetupOp *op; + + for (op = ops; op != NULL; op = op->next) + { + const char *old_source; + + switch (op->type) + { + case SETUP_RO_BIND_MOUNT: + case SETUP_DEV_BIND_MOUNT: + case SETUP_BIND_MOUNT: + case SETUP_OVERLAY_SRC: + case SETUP_OVERLAY_MOUNT: + old_source = op->source; + op->source = realpath (old_source, NULL); + if (op->source == NULL) + { + if (op->flags & ALLOW_NOTEXIST && errno == ENOENT) + op->source = old_source; + else + die_with_error("Can't find source path %s", old_source); + } + break; + + case SETUP_RO_OVERLAY_MOUNT: + case SETUP_TMP_OVERLAY_MOUNT: + case SETUP_MOUNT_PROC: + case SETUP_MOUNT_DEV: + case SETUP_MOUNT_TMPFS: + case SETUP_MOUNT_MQUEUE: + case SETUP_MAKE_DIR: + case SETUP_MAKE_FILE: + case SETUP_MAKE_BIND_FILE: + case SETUP_MAKE_RO_BIND_FILE: + case SETUP_MAKE_SYMLINK: + case SETUP_REMOUNT_RO_NO_RECURSIVE: + case SETUP_SET_HOSTNAME: + case SETUP_CHMOD: + default: + break; + } + } +} + + +static const char * +resolve_string_offset (void *buffer, + size_t buffer_size, + uint32_t offset) +{ + if (offset == 0) + return NULL; + + if (offset > buffer_size) + die ("Invalid string offset %d (buffer size %zd)", offset, buffer_size); + + return (const char *) buffer + offset; +} + +static uint32_t +read_priv_sec_op (int read_socket, + void *buffer, + size_t buffer_size, + uint32_t *flags, + uint32_t *perms, + size_t *size_arg, + const char **arg1, + const char **arg2) +{ + const PrivSepOp *op = (const PrivSepOp *) buffer; + ssize_t rec_len; + + do + rec_len = read (read_socket, buffer, buffer_size - 1); + while (rec_len == -1 && errno == EINTR); + + if (rec_len < 0) + die_with_error ("Can't read from unprivileged helper"); + + if (rec_len == 0) + exit (1); /* Privileged helper died and printed error, so exit silently */ + + if ((size_t)rec_len < sizeof (PrivSepOp)) + die ("Invalid size %zd from unprivileged helper", rec_len); + + /* Guarantee zero termination of any strings */ + ((char *) buffer)[rec_len] = 0; + + *flags = op->flags; + *perms = op->perms; + *size_arg = op->size_arg; + *arg1 = resolve_string_offset (buffer, rec_len, op->arg1_offset); + *arg2 = resolve_string_offset (buffer, rec_len, op->arg2_offset); + + return op->op; +} + +static void __attribute__ ((noreturn)) +print_version_and_exit (void) +{ + printf ("%s\n", PACKAGE_STRING); + exit (0); +} + +static int +is_modifier_option (const char *option) +{ + return strcmp (option, "--perms") == 0 + || strcmp(option, "--size") == 0; +} + +static void +warn_only_last_option (const char *name) +{ + warn ("Only the last %s option will take effect", name); +} + +static void +make_setup_overlay_src_ops (const char *const *const argv) +{ + /* SETUP_OVERLAY_SRC is unlike other SETUP_* ops in that it exists to hold + * data for SETUP_{,TMP_,RO_}OVERLAY_MOUNT ops, not to be its own operation. + * This lets us reuse existing code paths to handle resolving the realpaths + * of each source, as no other operations involve multiple sources the way + * the *_OVERLAY_MOUNT ops do. + * + * While the --overlay-src arguments are expected to (directly) precede the + * --overlay argument, in bottom-to-top order, the SETUP_OVERLAY_SRC ops + * follow their corresponding *_OVERLAY_MOUNT op, in top-to-bottom order + * (the order in which overlayfs will want them). They are handled specially + * in setup_new_root () during the processing of *_OVERLAY_MOUNT. + */ + int i; + SetupOp *op; + + for (i = 1; i <= next_overlay_src_count; i++) + { + op = setup_op_new (SETUP_OVERLAY_SRC); + op->source = argv[1 - 2 * i]; + } + next_overlay_src_count = 0; +} + +static void +parse_args_recurse (int *argcp, + const char ***argvp, + bool in_file, + int *total_parsed_argc_p) +{ + SetupOp *op; + int argc = *argcp; + const char **argv = *argvp; + /* I can't imagine a case where someone wants more than this. + * If you do...you should be able to pass multiple files + * via a single tmpfs and linking them there, etc. + * + * We're adding this hardening due to precedent from + * http://googleprojectzero.blogspot.com/2014/08/the-poisoned-nul-byte-2014-edition.html + * + * I picked 9000 because the Internet told me to and it was hard to + * resist. + */ + static const int32_t MAX_ARGS = 9000; + + if (*total_parsed_argc_p > MAX_ARGS) + die ("Exceeded maximum number of arguments %u", MAX_ARGS); + + while (argc > 0) + { + const char *arg = argv[0]; + + if (strcmp (arg, "--help") == 0) + { + usage (EXIT_SUCCESS, stdout); + } + else if (strcmp (arg, "--version") == 0) + { + print_version_and_exit (); + } + else if (strcmp (arg, "--args") == 0) + { + int the_fd; + char *endptr; + const char *p, *data_end; + size_t data_len; + cleanup_free const char **data_argv = NULL; + const char **data_argv_copy; + int data_argc; + int i; + + if (in_file) + die ("--args not supported in arguments file"); + + if (argc < 2) + die ("--args takes an argument"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + /* opt_args_data is essentially a recursive argv array, which we must + * keep allocated until exit time, since its argv entries get used + * by the other cases in parse_args_recurse() when we recurse. */ + opt_args_data = load_file_data (the_fd, &data_len); + if (opt_args_data == NULL) + die_with_error ("Can't read --args data"); + (void) close (the_fd); + + data_end = opt_args_data + data_len; + data_argc = 0; + + p = opt_args_data; + while (p != NULL && p < data_end) + { + data_argc++; + (*total_parsed_argc_p)++; + if (*total_parsed_argc_p > MAX_ARGS) + die ("Exceeded maximum number of arguments %u", MAX_ARGS); + p = memchr (p, 0, data_end - p); + if (p != NULL) + p++; + } + + data_argv = xcalloc (data_argc + 1, sizeof (char *)); + + i = 0; + p = opt_args_data; + while (p != NULL && p < data_end) + { + /* Note: load_file_data always adds a nul terminator, so this is safe + * even for the last string. */ + data_argv[i++] = p; + p = memchr (p, 0, data_end - p); + if (p != NULL) + p++; + } + + data_argv_copy = data_argv; /* Don't change data_argv, we need to free it */ + parse_args_recurse (&data_argc, &data_argv_copy, true, total_parsed_argc_p); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--argv0") == 0) + { + if (argc < 2) + die ("--argv0 takes one argument"); + + if (opt_argv0 != NULL) + die ("--argv0 used multiple times"); + + opt_argv0 = argv[1]; + argv++; + argc--; + } + else if (strcmp (arg, "--level-prefix") == 0) + { + bwrap_level_prefix = true; + } + else if (strcmp (arg, "--unshare-all") == 0) + { + /* Keep this in order with the older (legacy) --unshare arguments, + * we use the --try variants of user and cgroup, since we want + * to support systems/kernels without support for those. + */ + opt_unshare_user_try = opt_unshare_ipc = opt_unshare_pid = + opt_unshare_uts = opt_unshare_cgroup_try = + opt_unshare_net = true; + } + /* Begin here the older individual --unshare variants */ + else if (strcmp (arg, "--unshare-user") == 0) + { + opt_unshare_user = true; + } + else if (strcmp (arg, "--unshare-user-try") == 0) + { + opt_unshare_user_try = true; + } + else if (strcmp (arg, "--unshare-ipc") == 0) + { + opt_unshare_ipc = true; + } + else if (strcmp (arg, "--unshare-pid") == 0) + { + opt_unshare_pid = true; + } + else if (strcmp (arg, "--unshare-net") == 0) + { + opt_unshare_net = true; + } + else if (strcmp (arg, "--unshare-uts") == 0) + { + opt_unshare_uts = true; + } + else if (strcmp (arg, "--unshare-cgroup") == 0) + { + opt_unshare_cgroup = true; + } + else if (strcmp (arg, "--unshare-cgroup-try") == 0) + { + opt_unshare_cgroup_try = true; + } + /* Begin here the newer --share variants */ + else if (strcmp (arg, "--share-net") == 0) + { + opt_unshare_net = false; + } + /* End --share variants, other arguments begin */ + else if (strcmp (arg, "--chdir") == 0) + { + if (argc < 2) + die ("--chdir takes one argument"); + + if (opt_chdir_path != NULL) + warn_only_last_option ("--chdir"); + + opt_chdir_path = argv[1]; + argv++; + argc--; + } + else if (strcmp (arg, "--disable-userns") == 0) + { + opt_disable_userns = true; + } + else if (strcmp (arg, "--assert-userns-disabled") == 0) + { + opt_assert_userns_disabled = true; + } + else if (strcmp (arg, "--remount-ro") == 0) + { + if (argc < 2) + die ("--remount-ro takes one argument"); + + op = setup_op_new (SETUP_REMOUNT_RO_NO_RECURSIVE); + op->dest = argv[1]; + + argv++; + argc--; + } + else if (strcmp(arg, "--bind") == 0 || + strcmp(arg, "--bind-try") == 0) + { + if (argc < 3) + die ("%s takes two arguments", arg); + + op = setup_op_new (SETUP_BIND_MOUNT); + op->source = argv[1]; + op->dest = argv[2]; + if (strcmp(arg, "--bind-try") == 0) + op->flags = ALLOW_NOTEXIST; + + argv += 2; + argc -= 2; + } + else if (strcmp(arg, "--ro-bind") == 0 || + strcmp(arg, "--ro-bind-try") == 0) + { + if (argc < 3) + die ("%s takes two arguments", arg); + + op = setup_op_new (SETUP_RO_BIND_MOUNT); + op->source = argv[1]; + op->dest = argv[2]; + if (strcmp(arg, "--ro-bind-try") == 0) + op->flags = ALLOW_NOTEXIST; + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--dev-bind") == 0 || + strcmp (arg, "--dev-bind-try") == 0) + { + if (argc < 3) + die ("%s takes two arguments", arg); + + op = setup_op_new (SETUP_DEV_BIND_MOUNT); + op->source = argv[1]; + op->dest = argv[2]; + if (strcmp(arg, "--dev-bind-try") == 0) + op->flags = ALLOW_NOTEXIST; + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--bind-fd") == 0 || + strcmp (arg, "--ro-bind-fd") == 0) + { + int src_fd; + char *endptr; + + if (argc < 3) + die ("--bind-fd takes two arguments"); + + src_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || src_fd < 0) + die ("Invalid fd: %s", argv[1]); + + if (strcmp(arg, "--ro-bind-fd") == 0) + op = setup_op_new (SETUP_RO_BIND_MOUNT); + else + op = setup_op_new (SETUP_BIND_MOUNT); + op->source = xasprintf ("/proc/self/fd/%d", src_fd); + op->fd = src_fd; + op->dest = argv[2]; + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--overlay-src") == 0) + { + if (is_privileged) + die ("The --overlay-src option is not permitted in setuid mode"); + + next_overlay_src_count++; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--overlay") == 0) + { + SetupOp *workdir_op; + + if (is_privileged) + die ("The --overlay option is not permitted in setuid mode"); + + if (argc < 4) + die ("--overlay takes three arguments"); + + if (next_overlay_src_count < 1) + die ("--overlay requires at least one --overlay-src"); + + op = setup_op_new (SETUP_OVERLAY_MOUNT); + op->source = argv[1]; + workdir_op = setup_op_new (SETUP_OVERLAY_SRC); + workdir_op->source = argv[2]; + op->dest = argv[3]; + make_setup_overlay_src_ops (argv); + + argv += 3; + argc -= 3; + } + else if (strcmp (arg, "--tmp-overlay") == 0) + { + if (is_privileged) + die ("The --tmp-overlay option is not permitted in setuid mode"); + + if (argc < 2) + die ("--tmp-overlay takes an argument"); + + if (next_overlay_src_count < 1) + die ("--tmp-overlay requires at least one --overlay-src"); + + op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT); + op->dest = argv[1]; + make_setup_overlay_src_ops (argv); + opt_tmp_overlay_count++; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--ro-overlay") == 0) + { + if (is_privileged) + die ("The --ro-overlay option is not permitted in setuid mode"); + + if (argc < 2) + die ("--ro-overlay takes an argument"); + + if (next_overlay_src_count < 2) + die ("--ro-overlay requires at least two --overlay-src"); + + op = setup_op_new (SETUP_RO_OVERLAY_MOUNT); + op->dest = argv[1]; + make_setup_overlay_src_ops (argv); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--proc") == 0) + { + if (argc < 2) + die ("--proc takes an argument"); + + op = setup_op_new (SETUP_MOUNT_PROC); + op->dest = argv[1]; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--exec-label") == 0) + { + if (argc < 2) + die ("--exec-label takes an argument"); + + if (opt_exec_label != NULL) + warn_only_last_option ("--exec-label"); + + opt_exec_label = argv[1]; + die_unless_label_valid (opt_exec_label); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--file-label") == 0) + { + if (argc < 2) + die ("--file-label takes an argument"); + + if (opt_file_label != NULL) + warn_only_last_option ("--file-label"); + + opt_file_label = argv[1]; + die_unless_label_valid (opt_file_label); + if (label_create_file (opt_file_label)) + die_with_error ("--file-label setup failed"); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--dev") == 0) + { + if (argc < 2) + die ("--dev takes an argument"); + + op = setup_op_new (SETUP_MOUNT_DEV); + op->dest = argv[1]; + opt_needs_devpts = true; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--tmpfs") == 0) + { + if (argc < 2) + die ("--tmpfs takes an argument"); + + op = setup_op_new (SETUP_MOUNT_TMPFS); + op->dest = argv[1]; + + /* We historically hard-coded the mode of a tmpfs as 0755. */ + if (next_perms >= 0) + op->perms = next_perms; + else + op->perms = 0755; + + next_perms = -1; + + /* If the option is unset, next_size_arg is zero, which results in + * the default tmpfs size. This is exactly what we want. */ + op->size = next_size_arg; + + next_size_arg = 0; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--mqueue") == 0) + { + if (argc < 2) + die ("--mqueue takes an argument"); + + op = setup_op_new (SETUP_MOUNT_MQUEUE); + op->dest = argv[1]; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--dir") == 0) + { + if (argc < 2) + die ("--dir takes an argument"); + + op = setup_op_new (SETUP_MAKE_DIR); + op->dest = argv[1]; + + /* We historically hard-coded the mode of a --dir as 0755. */ + if (next_perms >= 0) + op->perms = next_perms; + else + op->perms = 0755; + + next_perms = -1; + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--file") == 0) + { + int file_fd; + char *endptr; + + if (argc < 3) + die ("--file takes two arguments"); + + file_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0) + die ("Invalid fd: %s", argv[1]); + + op = setup_op_new (SETUP_MAKE_FILE); + op->fd = file_fd; + op->dest = argv[2]; + + /* We historically hard-coded the mode of a --file as 0666. */ + if (next_perms >= 0) + op->perms = next_perms; + else + op->perms = 0666; + + next_perms = -1; + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--bind-data") == 0) + { + int file_fd; + char *endptr; + + if (argc < 3) + die ("--bind-data takes two arguments"); + + file_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0) + die ("Invalid fd: %s", argv[1]); + + op = setup_op_new (SETUP_MAKE_BIND_FILE); + op->fd = file_fd; + op->dest = argv[2]; + + /* This is consistent with previous bubblewrap behaviour: + * before implementing --perms, we took the permissions + * given to us by mkstemp(), which are documented to be 0600. */ + if (next_perms >= 0) + op->perms = next_perms; + else + op->perms = 0600; + + next_perms = -1; + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--ro-bind-data") == 0) + { + int file_fd; + char *endptr; + + if (argc < 3) + die ("--ro-bind-data takes two arguments"); + + file_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || file_fd < 0) + die ("Invalid fd: %s", argv[1]); + + op = setup_op_new (SETUP_MAKE_RO_BIND_FILE); + op->fd = file_fd; + op->dest = argv[2]; + + /* This is consistent with previous bubblewrap behaviour: + * before implementing --perms, we took the permissions + * given to us by mkstemp(), which are documented to be 0600. */ + if (next_perms >= 0) + op->perms = next_perms; + else + op->perms = 0600; + + next_perms = -1; + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--symlink") == 0) + { + if (argc < 3) + die ("--symlink takes two arguments"); + + op = setup_op_new (SETUP_MAKE_SYMLINK); + op->source = argv[1]; + op->dest = argv[2]; + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--lock-file") == 0) + { + if (argc < 2) + die ("--lock-file takes an argument"); + + (void) lock_file_new (argv[1]); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--sync-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--sync-fd takes an argument"); + + if (opt_sync_fd != -1) + warn_only_last_option ("--sync-fd"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_sync_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--block-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--block-fd takes an argument"); + + if (opt_block_fd != -1) + warn_only_last_option ("--block-fd"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_block_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--userns-block-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--userns-block-fd takes an argument"); + + if (opt_userns_block_fd != -1) + warn_only_last_option ("--userns-block-fd"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_userns_block_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--info-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--info-fd takes an argument"); + + if (opt_info_fd != -1) + warn_only_last_option ("--info-fd"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_info_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--json-status-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--json-status-fd takes an argument"); + + if (opt_json_status_fd != -1) + warn_only_last_option ("--json-status-fd"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_json_status_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--seccomp") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--seccomp takes an argument"); + + if (seccomp_programs != NULL) + die ("--seccomp cannot be combined with --add-seccomp-fd"); + + if (opt_seccomp_fd != -1) + warn_only_last_option ("--seccomp"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_seccomp_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--add-seccomp-fd") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--add-seccomp-fd takes an argument"); + + if (opt_seccomp_fd != -1) + die ("--add-seccomp-fd cannot be combined with --seccomp"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + /* takes ownership of fd */ + seccomp_program_new (&the_fd); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--userns") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--userns takes an argument"); + + if (opt_userns_fd != -1) + warn_only_last_option ("--userns"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_userns_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--userns2") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--userns2 takes an argument"); + + if (opt_userns2_fd != -1) + warn_only_last_option ("--userns2"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_userns2_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--pidns") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--pidns takes an argument"); + + if (opt_pidns_fd != -1) + warn_only_last_option ("--pidns"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_pidns_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--clearenv") == 0) + { + xclearenv (); + } + else if (strcmp (arg, "--setenv") == 0) + { + if (argc < 3) + die ("--setenv takes two arguments"); + + xsetenv (argv[1], argv[2], 1); + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--unsetenv") == 0) + { + if (argc < 2) + die ("--unsetenv takes an argument"); + + xunsetenv (argv[1]); + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--uid") == 0) + { + int the_uid; + char *endptr; + + if (argc < 2) + die ("--uid takes an argument"); + + if (opt_sandbox_uid != (uid_t)-1) + warn_only_last_option ("--uid"); + + the_uid = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_uid < 0) + die ("Invalid uid: %s", argv[1]); + + opt_sandbox_uid = the_uid; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--gid") == 0) + { + int the_gid; + char *endptr; + + if (argc < 2) + die ("--gid takes an argument"); + + if (opt_sandbox_gid != (gid_t)-1) + warn_only_last_option ("--gid"); + + the_gid = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_gid < 0) + die ("Invalid gid: %s", argv[1]); + + opt_sandbox_gid = the_gid; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--hostname") == 0) + { + if (argc < 2) + die ("--hostname takes an argument"); + + if (opt_sandbox_hostname != NULL) + warn_only_last_option ("--hostname"); + + op = setup_op_new (SETUP_SET_HOSTNAME); + op->dest = argv[1]; + op->flags = NO_CREATE_DEST; + + opt_sandbox_hostname = argv[1]; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--new-session") == 0) + { + opt_new_session = true; + } + else if (strcmp (arg, "--die-with-parent") == 0) + { + opt_die_with_parent = true; + } + else if (strcmp (arg, "--as-pid-1") == 0) + { + opt_as_pid_1 = true; + } + else if (strcmp (arg, "--cap-add") == 0) + { + cap_value_t cap; + if (argc < 2) + die ("--cap-add takes an argument"); + + opt_cap_add_or_drop_used = true; + + if (strcasecmp (argv[1], "ALL") == 0) + { + requested_caps[0] = requested_caps[1] = 0xFFFFFFFF; + } + else + { + if (cap_from_name (argv[1], &cap) < 0) + die ("unknown cap: %s", argv[1]); + + if (cap < 32) + requested_caps[0] |= CAP_TO_MASK_0 (cap); + else + requested_caps[1] |= CAP_TO_MASK_1 (cap - 32); + } + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--cap-drop") == 0) + { + cap_value_t cap; + if (argc < 2) + die ("--cap-drop takes an argument"); + + opt_cap_add_or_drop_used = true; + + if (strcasecmp (argv[1], "ALL") == 0) + { + requested_caps[0] = requested_caps[1] = 0; + } + else + { + if (cap_from_name (argv[1], &cap) < 0) + die ("unknown cap: %s", argv[1]); + + if (cap < 32) + requested_caps[0] &= ~CAP_TO_MASK_0 (cap); + else + requested_caps[1] &= ~CAP_TO_MASK_1 (cap - 32); + } + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--perms") == 0) + { + unsigned long perms; + char *endptr = NULL; + + if (argc < 2) + die ("--perms takes an argument"); + + if (next_perms != -1) + die ("--perms given twice for the same action"); + + perms = strtoul (argv[1], &endptr, 8); + + if (argv[1][0] == '\0' + || endptr == NULL + || *endptr != '\0' + || perms > 07777) + die ("--perms takes an octal argument <= 07777"); + + next_perms = (int) perms; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--size") == 0) + { + unsigned long long size; + char *endptr = NULL; + + if (is_privileged) + die ("The --size option is not permitted in setuid mode"); + + if (argc < 2) + die ("--size takes an argument"); + + if (next_size_arg != 0) + die ("--size given twice for the same action"); + + errno = 0; /* reset errno so we can detect ERANGE from strtoull */ + + size = strtoull (argv[1], &endptr, 0); + + /* isdigit: Not only check that the first digit is not '\0', but + * simultaneously guard against negative numbers or preceding + * spaces. */ + if (errno != 0 /* from strtoull */ + || !isdigit(argv[1][0]) + || endptr == NULL + || *endptr != '\0' + || size == 0) + die ("--size takes a non-zero number of bytes"); + + if (size > MAX_TMPFS_BYTES) + die ("--size (for tmpfs) is limited to %zu", MAX_TMPFS_BYTES); + + next_size_arg = (size_t) size; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--chmod") == 0) + { + unsigned long perms; + char *endptr = NULL; + + if (argc < 3) + die ("--chmod takes two arguments"); + + perms = strtoul (argv[1], &endptr, 8); + + if (argv[1][0] == '\0' + || endptr == NULL + || *endptr != '\0' + || perms > 07777) + die ("--chmod takes an octal argument <= 07777"); + + op = setup_op_new (SETUP_CHMOD); + op->flags = NO_CREATE_DEST; + op->perms = (int) perms; + op->dest = argv[2]; + + argv += 2; + argc -= 2; + } + else if (strcmp (arg, "--") == 0) + { + argv += 1; + argc -= 1; + break; + } + else if (*arg == '-') + { + die ("Unknown option %s", arg); + } + else + { + break; + } + + /* If --perms was set for the current action but the current action + * didn't consume the setting, apparently --perms wasn't suitable for + * this action. */ + if (!is_modifier_option(arg) && next_perms >= 0) + die ("--perms must be followed by an option that creates a file"); + + /* Similarly for --size. */ + if (!is_modifier_option(arg) && next_size_arg != 0) + die ("--size must be followed by --tmpfs"); + + /* Similarly for --overlay-src. */ + if (strcmp (arg, "--overlay-src") != 0 && next_overlay_src_count > 0) + die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"); + + argv++; + argc--; + } + + *argcp = argc; + *argvp = argv; +} + +static void +parse_args (int *argcp, + const char ***argvp) +{ + int total_parsed_argc = *argcp; + + parse_args_recurse (argcp, argvp, false, &total_parsed_argc); + + if (next_overlay_src_count > 0) + die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"); +} + +static void +read_overflowids (void) +{ + cleanup_free char *uid_data = NULL; + cleanup_free char *gid_data = NULL; + + uid_data = load_file_at (AT_FDCWD, "/proc/sys/kernel/overflowuid"); + if (uid_data == NULL) + die_with_error ("Can't read /proc/sys/kernel/overflowuid"); + + overflow_uid = strtol (uid_data, NULL, 10); + if (overflow_uid == 0) + die ("Can't parse /proc/sys/kernel/overflowuid"); + + gid_data = load_file_at (AT_FDCWD, "/proc/sys/kernel/overflowgid"); + if (gid_data == NULL) + die_with_error ("Can't read /proc/sys/kernel/overflowgid"); + + overflow_gid = strtol (gid_data, NULL, 10); + if (overflow_gid == 0) + die ("Can't parse /proc/sys/kernel/overflowgid"); +} + +static void +namespace_ids_read (pid_t pid) +{ + cleanup_free char *dir = NULL; + cleanup_fd int ns_fd = -1; + NsInfo *info; + + dir = xasprintf ("%d/ns", pid); + ns_fd = TEMP_FAILURE_RETRY (openat (proc_fd, dir, O_PATH)); + + if (ns_fd < 0) + die_with_error ("open /proc/%s/ns failed", dir); + + for (info = ns_infos; info->name; info++) + { + bool *do_unshare = info->do_unshare; + struct stat st; + int r; + + /* if we don't unshare this ns, ignore it */ + if (do_unshare && *do_unshare == false) + continue; + + r = fstatat (ns_fd, info->name, &st, 0); + + /* if we can't get the information, ignore it */ + if (r != 0) + continue; + + info->id = st.st_ino; + } +} + +static void +namespace_ids_write (int fd, + bool in_json) +{ + NsInfo *info; + + for (info = ns_infos; info->name; info++) + { + cleanup_free char *output = NULL; + const char *indent; + uintmax_t nsid; + + nsid = (uintmax_t) info->id; + + /* if we don't have the information, we don't write it */ + if (nsid == 0) + continue; + + indent = in_json ? " " : "\n "; + output = xasprintf (",%s\"%s-namespace\": %ju", + indent, info->name, nsid); + + dump_info (fd, output, true); + } +} + +int +main (int argc, + char **argv) +{ + mode_t old_umask; + const char *base_path = NULL; + int clone_flags; + char *old_cwd = NULL; + pid_t pid; + int event_fd = -1; + int child_wait_fd = -1; + int setup_finished_pipe[] = {-1, -1}; + const char *new_cwd; + uid_t ns_uid; + gid_t ns_gid; + struct stat sbuf; + uint64_t val; + int res UNUSED; + cleanup_free char *args_data UNUSED = NULL; + int intermediate_pids_sockets[2] = {-1, -1}; + const char *exec_path = NULL; + int i; + struct sigaction sa = {}; + + /* Handle --version early on before we try to acquire/drop + * any capabilities so it works in a build environment; + * right now flatpak's build runs bubblewrap --version. + * https://github.com/projectatomic/bubblewrap/issues/185 + */ + if (argc == 2 && (strcmp (argv[1], "--version") == 0)) + print_version_and_exit (); + + /* Reset SIGCHILD to SIG_DFL allowing signalfd working propertly + * if the parent process had set SIGCHLD to SIG_IGN. */ + sigemptyset (&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction (SIGCHLD, &sa, NULL); + + real_uid = getuid (); + real_gid = getgid (); + + /* Get the (optional) privileges we need */ + acquire_privs (); + + /* Never gain any more privs during exec */ + if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) + die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed"); + + /* The initial code is run with high permissions + (i.e. CAP_SYS_ADMIN), so take lots of care. */ + + read_overflowids (); + + argv0 = argv[0]; + + if (isatty (1)) + host_tty_dev = ttyname (1); + + argv++; + argc--; + + if (argc <= 0) + usage (EXIT_FAILURE, stderr); + + parse_args (&argc, (const char ***) &argv); + + /* suck the args into a cleanup_free variable to control their lifecycle */ + args_data = opt_args_data; + opt_args_data = NULL; + + if ((requested_caps[0] || requested_caps[1]) && is_privileged) + die ("--cap-add in setuid mode can be used only by root"); + + if (opt_userns_block_fd != -1 && !opt_unshare_user) + die ("--userns-block-fd requires --unshare-user"); + + if (opt_userns_block_fd != -1 && opt_info_fd == -1) + die ("--userns-block-fd requires --info-fd"); + + if (opt_userns_fd != -1 && opt_unshare_user) + die ("--userns not compatible --unshare-user"); + + if (opt_userns_fd != -1 && opt_unshare_user_try) + die ("--userns not compatible --unshare-user-try"); + + if (opt_disable_userns && !opt_unshare_user) + die ("--disable-userns requires --unshare-user"); + + if (opt_disable_userns && opt_userns_block_fd != -1) + die ("--disable-userns is not compatible with --userns-block-fd"); + + /* Technically using setns() is probably safe even in the privileged + * case, because we got passed in a file descriptor to the + * namespace, and that can only be gotten if you have ptrace + * permissions against the target, and then you could do whatever to + * the namespace anyway. + * + * However, for practical reasons this isn't possible to use, + * because (as described in acquire_privs()) setuid bwrap causes + * root to own the namespaces that it creates, so you will not be + * able to access these namespaces anyway. So, best just not support + * it anyway. + */ + if (opt_userns_fd != -1 && is_privileged) + die ("--userns doesn't work in setuid mode"); + + if (opt_userns2_fd != -1 && is_privileged) + die ("--userns2 doesn't work in setuid mode"); + + /* We have to do this if we weren't installed setuid (and we're not + * root), so let's just DWIM */ + if (!is_privileged && getuid () != 0 && opt_userns_fd == -1) + opt_unshare_user = true; + +#ifdef ENABLE_REQUIRE_USERNS + /* In this build option, we require userns. */ + if (is_privileged && getuid () != 0 && opt_userns_fd == -1) + opt_unshare_user = true; +#endif + + if (opt_unshare_user_try && + stat ("/proc/self/ns/user", &sbuf) == 0) + { + bool disabled = false; + + /* RHEL7 has a kernel module parameter that lets you enable user namespaces */ + if (stat ("/sys/module/user_namespace/parameters/enable", &sbuf) == 0) + { + cleanup_free char *enable = NULL; + enable = load_file_at (AT_FDCWD, "/sys/module/user_namespace/parameters/enable"); + if (enable != NULL && enable[0] == 'N') + disabled = true; + } + + /* Check for max_user_namespaces */ + if (stat ("/proc/sys/user/max_user_namespaces", &sbuf) == 0) + { + cleanup_free char *max_user_ns = NULL; + max_user_ns = load_file_at (AT_FDCWD, "/proc/sys/user/max_user_namespaces"); + if (max_user_ns != NULL && strcmp(max_user_ns, "0\n") == 0) + disabled = true; + } + + /* Debian lets you disable *unprivileged* user namespaces. However this is not + a problem if we're privileged, and if we're not opt_unshare_user is true + already, and there is not much we can do, its just a non-working setup. */ + + if (!disabled) + opt_unshare_user = true; + } + + if (argc <= 0) + usage (EXIT_FAILURE, stderr); + + debug ("Creating root mount point"); + + if (opt_sandbox_uid == (uid_t)-1) + opt_sandbox_uid = real_uid; + if (opt_sandbox_gid == (gid_t)-1) + opt_sandbox_gid = real_gid; + + if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_uid != real_uid) + die ("Specifying --uid requires --unshare-user or --userns"); + + if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_gid != real_gid) + die ("Specifying --gid requires --unshare-user or --userns"); + + if (!opt_unshare_uts && opt_sandbox_hostname != NULL) + die ("Specifying --hostname requires --unshare-uts"); + + if (opt_as_pid_1 && !opt_unshare_pid) + die ("Specifying --as-pid-1 requires --unshare-pid"); + + if (opt_as_pid_1 && lock_files != NULL) + die ("Specifying --as-pid-1 and --lock-file is not permitted"); + + /* We need to read stuff from proc during the pivot_root dance, etc. + Lets keep a fd to it open */ + proc_fd = TEMP_FAILURE_RETRY (open ("/proc", O_PATH)); + if (proc_fd == -1) + die_with_error ("Can't open /proc"); + + /* We need *some* mountpoint where we can mount the root tmpfs. + * Because we use pivot_root, it won't appear to be mounted from + * the perspective of the sandboxed process, so we can use anywhere + * that is sure to exist, that is sure to not be a symlink controlled + * by someone malicious, and that we won't immediately need to + * access ourselves. */ + base_path = "/tmp"; + + debug ("creating new namespace"); + + if (opt_unshare_pid && !opt_as_pid_1) + { + event_fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); + if (event_fd == -1) + die_with_error ("eventfd()"); + } + + /* We block sigchild here so that we can use signalfd in the monitor. */ + block_sigchild (); + + clone_flags = SIGCHLD | CLONE_NEWNS; + if (opt_unshare_user) + clone_flags |= CLONE_NEWUSER; + if (opt_unshare_pid && opt_pidns_fd == -1) + clone_flags |= CLONE_NEWPID; + if (opt_unshare_net) + clone_flags |= CLONE_NEWNET; + if (opt_unshare_ipc) + clone_flags |= CLONE_NEWIPC; + if (opt_unshare_uts) + clone_flags |= CLONE_NEWUTS; + if (opt_unshare_cgroup) + { + if (stat ("/proc/self/ns/cgroup", &sbuf)) + { + if (errno == ENOENT) + die ("Cannot create new cgroup namespace because the kernel does not support it"); + else + die_with_error ("stat on /proc/self/ns/cgroup failed"); + } + clone_flags |= CLONE_NEWCGROUP; + } + if (opt_unshare_cgroup_try) + { + opt_unshare_cgroup = !stat ("/proc/self/ns/cgroup", &sbuf); + if (opt_unshare_cgroup) + clone_flags |= CLONE_NEWCGROUP; + } + + child_wait_fd = eventfd (0, EFD_CLOEXEC); + if (child_wait_fd == -1) + die_with_error ("eventfd()"); + + /* Track whether pre-exec setup finished if we're reporting process exit */ + if (opt_json_status_fd != -1) + { + int ret; + ret = pipe2 (setup_finished_pipe, O_CLOEXEC); + if (ret == -1) + die_with_error ("pipe2()"); + } + + /* Switch to the custom user ns before the clone, gets us privs in that ns (assuming its a child of the current and thus allowed) */ + if (opt_userns_fd > 0 && setns (opt_userns_fd, CLONE_NEWUSER) != 0) + { + if (errno == EINVAL) + die ("Joining the specified user namespace failed, it might not be a descendant of the current user namespace."); + die_with_error ("Joining specified user namespace failed"); + } + + /* Sometimes we have uninteresting intermediate pids during the setup, set up code to pass the real pid down */ + if (opt_pidns_fd != -1) + { + /* Mark us as a subreaper, this way we can get exit status from grandchildren */ + prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); + create_pid_socketpair (intermediate_pids_sockets); + } + + pid = raw_clone (clone_flags, NULL); + if (pid == -1) + { + if (opt_unshare_user) + { + if (errno == EINVAL) + die ("Creating new namespace failed, likely because the kernel does not support user namespaces. bwrap must be installed setuid on such systems."); + else if (errno == EPERM && !is_privileged) + die ("No permissions to create a new namespace, likely because the kernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'."); + } + + if (errno == ENOSPC) + die ("Creating new namespace failed: nesting depth or /proc/sys/user/max_*_namespaces exceeded (ENOSPC)"); + + die_with_error ("Creating new namespace failed"); + } + + ns_uid = opt_sandbox_uid; + ns_gid = opt_sandbox_gid; + + if (pid != 0) + { + /* Parent, outside sandbox, privileged (initially) */ + + if (intermediate_pids_sockets[0] != -1) + { + close (intermediate_pids_sockets[1]); + pid = read_pid_from_socket (intermediate_pids_sockets[0]); + close (intermediate_pids_sockets[0]); + } + + /* Discover namespace ids before we drop privileges */ + namespace_ids_read (pid); + + if (is_privileged && opt_unshare_user && opt_userns_block_fd == -1) + { + /* We're running as euid 0, but the uid we want to map is + * not 0. This means we're not allowed to write this from + * the child user namespace, so we do it from the parent. + * + * Also, we map uid/gid 0 in the namespace (to overflowuid) + * if opt_needs_devpts is true, because otherwise the mount + * of devpts fails due to root not being mapped. + */ + write_uid_gid_map (ns_uid, real_uid, + ns_gid, real_gid, + pid, true, opt_needs_devpts); + } + + /* Initial launched process, wait for pid 1 or exec:ed command to exit */ + + if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0) + die_with_error ("Setting userns2 failed"); + + /* We don't need any privileges in the launcher, drop them immediately. */ + drop_privs (false, false); + + /* Optionally bind our lifecycle to that of the parent */ + handle_die_with_parent (); + + if (opt_info_fd != -1) + { + cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i", pid); + dump_info (opt_info_fd, output, true); + namespace_ids_write (opt_info_fd, false); + dump_info (opt_info_fd, "\n}\n", true); + close (opt_info_fd); + } + if (opt_json_status_fd != -1) + { + cleanup_free char *output = xasprintf ("{ \"child-pid\": %i", pid); + dump_info (opt_json_status_fd, output, true); + namespace_ids_write (opt_json_status_fd, true); + dump_info (opt_json_status_fd, " }\n", true); + } + + if (opt_userns_block_fd != -1) + { + char b[1]; + (void) TEMP_FAILURE_RETRY (read (opt_userns_block_fd, b, 1)); + close (opt_userns_block_fd); + } + + /* Let child run now that the uid maps are set up */ + val = 1; + res = TEMP_FAILURE_RETRY (write (child_wait_fd, &val, 8)); + /* Ignore res, if e.g. the child died and closed child_wait_fd we don't want to error out here */ + close (child_wait_fd); + + return monitor_child (event_fd, pid, setup_finished_pipe[0]); + } + + if (opt_pidns_fd > 0) + { + if (setns (opt_pidns_fd, CLONE_NEWPID) != 0) + die_with_error ("Setting pidns failed"); + + /* fork to get the passed in pid ns */ + fork_intermediate_child (); + + /* We might both have specified an --pidns *and* --unshare-pid, so set up a new child pid namespace under the specified one */ + if (opt_unshare_pid) + { + if (unshare (CLONE_NEWPID)) + die_with_error ("unshare pid ns"); + + /* fork to get the new pid ns */ + fork_intermediate_child (); + } + + /* We're back, either in a child or grandchild, so message the actual pid to the monitor */ + + close (intermediate_pids_sockets[0]); + send_pid_on_socket (intermediate_pids_sockets[1]); + close (intermediate_pids_sockets[1]); + } + + /* Child, in sandbox, privileged in the parent or in the user namespace (if --unshare-user). + * + * Note that for user namespaces we run as euid 0 during clone(), so + * the child user namespace is owned by euid 0., This means that the + * regular user namespace parent (with uid != 0) doesn't have any + * capabilities in it, which is nice as we can't exploit those. In + * particular the parent user namespace doesn't have CAP_PTRACE + * which would otherwise allow the parent to hijack of the child + * after this point. + * + * Unfortunately this also means you can't ptrace the final + * sandboxed process from outside the sandbox either. + */ + + if (opt_info_fd != -1) + close (opt_info_fd); + + if (opt_json_status_fd != -1) + close (opt_json_status_fd); + + /* Wait for the parent to init uid/gid maps and drop caps */ + res = read (child_wait_fd, &val, 8); + close (child_wait_fd); + + /* At this point we can completely drop root uid, but retain the + * required permitted caps. This allow us to do full setup as + * the user uid, which makes e.g. fuse access work. + */ + switch_to_user_with_privs (); + + if (opt_unshare_net) + loopback_setup (); /* Will exit if unsuccessful */ + + ns_uid = opt_sandbox_uid; + ns_gid = opt_sandbox_gid; + if (!is_privileged && opt_unshare_user && opt_userns_block_fd == -1) + { + /* In the unprivileged case we have to write the uid/gid maps in + * the child, because we have no caps in the parent */ + + if (opt_needs_devpts) + { + /* This is a bit hacky, but we need to first map the real uid/gid to + 0, otherwise we can't mount the devpts filesystem because root is + not mapped. Later we will create another child user namespace and + map back to the real uid */ + ns_uid = 0; + ns_gid = 0; + } + + write_uid_gid_map (ns_uid, real_uid, + ns_gid, real_gid, + -1, true, false); + } + + old_umask = umask (0); + + /* Need to do this before the chroot, but after we're the real uid */ + resolve_symlinks_in_ops (); + + /* Mark everything as slave, so that we still + * receive mounts from the real root, but don't + * propagate mounts to the real root. */ + if (mount (NULL, "/", NULL, MS_SILENT | MS_SLAVE | MS_REC, NULL) < 0) + die_with_mount_error ("Failed to make / slave"); + + /* Create a tmpfs which we will use as / in the namespace */ + if (mount ("tmpfs", base_path, "tmpfs", MS_NODEV | MS_NOSUID, NULL) != 0) + die_with_mount_error ("Failed to mount tmpfs"); + + old_cwd = get_current_dir_name (); + + /* Chdir to the new root tmpfs mount. This will be the CWD during + the entire setup. Access old or new root via "oldroot" and "newroot". */ + if (chdir (base_path) != 0) + die_with_error ("chdir base_path"); + + /* We create a subdir "$base_path/newroot" for the new root, that + * way we can pivot_root to base_path, and put the old root at + * "$base_path/oldroot". This avoids problems accessing the oldroot + * dir if the user requested to bind mount something over / (or + * over /tmp, now that we use that for base_path). */ + + if (mkdir ("newroot", 0755)) + die_with_error ("Creating newroot failed"); + + if (mount ("newroot", "newroot", NULL, MS_SILENT | MS_MGC_VAL | MS_BIND | MS_REC, NULL) < 0) + die_with_mount_error ("setting up newroot bind"); + + if (mkdir ("oldroot", 0755)) + die_with_error ("Creating oldroot failed"); + + for (i = 0; i < opt_tmp_overlay_count; i++) + { + char *dirname; + dirname = xasprintf ("tmp-overlay-upper-%d", i); + if (mkdir (dirname, 0755)) + die_with_error ("Creating --tmp-overlay upperdir failed"); + free (dirname); + dirname = xasprintf ("tmp-overlay-work-%d", i); + if (mkdir (dirname, 0755)) + die_with_error ("Creating --tmp-overlay workdir failed"); + free (dirname); + } + + if (pivot_root (base_path, "oldroot")) + die_with_error ("pivot_root"); + + if (chdir ("/") != 0) + die_with_error ("chdir / (base path)"); + + if (is_privileged) + { + pid_t child; + int privsep_sockets[2]; + + if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, privsep_sockets) != 0) + die_with_error ("Can't create privsep socket"); + + child = fork (); + if (child == -1) + die_with_error ("Can't fork unprivileged helper"); + + if (child == 0) + { + /* Unprivileged setup process */ + drop_privs (false, true); + close (privsep_sockets[0]); + setup_newroot (opt_unshare_pid, privsep_sockets[1]); + exit (0); + } + else + { + int status; + uint32_t buffer[2048]; /* 8k, but is int32 to guarantee nice alignment */ + uint32_t op, flags, perms; + size_t size_arg; + const char *arg1, *arg2; + cleanup_fd int unpriv_socket = -1; + + unpriv_socket = privsep_sockets[0]; + close (privsep_sockets[1]); + + do + { + op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer), + &flags, &perms, &size_arg, &arg1, &arg2); + privileged_op (-1, op, flags, perms, size_arg, arg1, arg2); + if (TEMP_FAILURE_RETRY (write (unpriv_socket, buffer, 1)) != 1) + die ("Can't write to op_socket"); + } + while (op != PRIV_SEP_OP_DONE); + + TEMP_FAILURE_RETRY (waitpid (child, &status, 0)); + /* Continue post setup */ + } + } + else + { + setup_newroot (opt_unshare_pid, -1); + } + + close_ops_fd (); + + /* The old root better be rprivate or we will send unmount events to the parent namespace */ + if (mount ("oldroot", "oldroot", NULL, MS_SILENT | MS_REC | MS_PRIVATE, NULL) != 0) + die_with_mount_error ("Failed to make old root rprivate"); + + if (umount2 ("oldroot", MNT_DETACH)) + die_with_error ("unmount old root"); + + /* This is our second pivot. It's like we're a Silicon Valley startup flush + * with cash but short on ideas! + * + * We're aiming to make /newroot the real root, and get rid of /oldroot. To do + * that we need a temporary place to store it before we can unmount it. + */ + { cleanup_fd int oldrootfd = TEMP_FAILURE_RETRY (open ("/", O_DIRECTORY | O_RDONLY)); + if (oldrootfd < 0) + die_with_error ("can't open /"); + if (chdir ("/newroot") != 0) + die_with_error ("chdir /newroot"); + /* While the documentation claims that put_old must be underneath + * new_root, it is perfectly fine to use the same directory as the + * kernel checks only if old_root is accessible from new_root. + * + * Both runc and LXC are using this "alternative" method for + * setting up the root of the container: + * + * https://github.com/opencontainers/runc/blob/HEAD/libcontainer/rootfs_linux.go#L671 + * https://github.com/lxc/lxc/blob/HEAD/src/lxc/conf.c#L1121 + */ + if (pivot_root (".", ".") != 0) + die_with_error ("pivot_root(/newroot)"); + if (fchdir (oldrootfd) < 0) + die_with_error ("fchdir to oldroot"); + if (umount2 (".", MNT_DETACH) < 0) + die_with_error ("umount old root"); + if (chdir ("/") != 0) + die_with_error ("chdir /"); + } + + if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0) + die_with_error ("Setting userns2 failed"); + + if (opt_unshare_user && opt_userns_block_fd == -1 && + (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid || + opt_disable_userns)) + { + /* Here we create a second level userns inside the first one. This is + used for one or more of these reasons: + + * The 1st level namespace has a different uid/gid than the + requested due to requirements of beeing root in the first + level due for mounting devpts (opt_needs_devpts). + + * To disable user namespaces we set max_user_namespaces and then + create the second namespace so that the sandbox cannot undo this + change. + */ + + if (opt_disable_userns) + { + cleanup_fd int sysctl_fd = -1; + + sysctl_fd = TEMP_FAILURE_RETRY (openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY)); + + if (sysctl_fd < 0) + die_with_error ("cannot open /proc/sys/user/max_user_namespaces"); + + if (write_to_fd (sysctl_fd, "1", 1) < 0) + die_with_error ("sysctl user.max_user_namespaces = 1"); + } + + if (unshare (CLONE_NEWUSER)) + die_with_error ("unshare user ns"); + + /* We're in a new user namespace, we got back the bounding set, clear it again */ + drop_cap_bounding_set (false); + + write_uid_gid_map (opt_sandbox_uid, ns_uid, + opt_sandbox_gid, ns_gid, + -1, false, false); + } + + if (opt_disable_userns || opt_assert_userns_disabled) + { + /* Verify that we can't make a new userns again */ + res = unshare (CLONE_NEWUSER); + + if (res == 0) + die ("creation of new user namespaces was not disabled as requested"); + } + + /* All privileged ops are done now, so drop caps we don't need */ + drop_privs (!is_privileged, true); + + if (opt_block_fd != -1) + { + char b[1]; + (void) TEMP_FAILURE_RETRY (read (opt_block_fd, b, 1)); + close (opt_block_fd); + } + + if (opt_seccomp_fd != -1) + { + assert (seccomp_programs == NULL); + /* takes ownership of fd */ + seccomp_program_new (&opt_seccomp_fd); + } + + umask (old_umask); + + new_cwd = "/"; + if (opt_chdir_path) + { + if (chdir (opt_chdir_path)) + die_with_error ("Can't chdir to %s", opt_chdir_path); + new_cwd = opt_chdir_path; + } + else if (chdir (old_cwd) == 0) + { + /* If the old cwd is mapped in the sandbox, go there */ + new_cwd = old_cwd; + } + else + { + /* If the old cwd is not mapped, go to home */ + const char *home = getenv ("HOME"); + if (home != NULL && + chdir (home) == 0) + new_cwd = home; + } + xsetenv ("PWD", new_cwd, 1); + free (old_cwd); + + if (opt_new_session && + setsid () == (pid_t) -1) + die_with_error ("setsid"); + + if (label_exec (opt_exec_label) == -1) + die_with_error ("label_exec %s", argv[0]); + + debug ("forking for child"); + + if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1)) + { + /* We have to have a pid 1 in the pid namespace, because + * otherwise we'll get a bunch of zombies as nothing reaps + * them. Alternatively if we're using sync_fd or lock_files we + * need some process to own these. + */ + + pid = fork (); + if (pid == -1) + die_with_error ("Can't fork for pid 1"); + + if (pid != 0) + { + drop_all_caps (false); + + /* Close fds in pid 1, except stdio and optionally event_fd + (for syncing pid 2 lifetime with monitor_child) and + opt_sync_fd (for syncing sandbox lifetime with outside + process). + Any other fds will been passed on to the child though. */ + { + int dont_close[3]; + int j = 0; + if (event_fd != -1) + dont_close[j++] = event_fd; + if (opt_sync_fd != -1) + dont_close[j++] = opt_sync_fd; + dont_close[j++] = -1; + fdwalk (proc_fd, close_extra_fds, dont_close); + } + + return do_init (event_fd, pid); + } + } + + debug ("launch executable %s", argv[0]); + + if (proc_fd != -1) + close (proc_fd); + + /* If we are using --as-pid-1 leak the sync fd into the sandbox. + --sync-fd will still work unless the container process doesn't close this file. */ + if (!opt_as_pid_1) + { + if (opt_sync_fd != -1) + close (opt_sync_fd); + } + + /* We want sigchild in the child */ + unblock_sigchild (); + + /* Optionally bind our lifecycle */ + handle_die_with_parent (); + + if (!is_privileged) + set_ambient_capabilities (); + + /* Should be the last thing before execve() so that filters don't + * need to handle anything above */ + seccomp_programs_apply (); + + if (setup_finished_pipe[1] != -1) + { + char data = 0; + res = write_to_fd (setup_finished_pipe[1], &data, 1); + /* Ignore res, if e.g. the parent died and closed setup_finished_pipe[0] + we don't want to error out here */ + } + + exec_path = argv[0]; + if (opt_argv0 != NULL) + argv[0] = (char *) opt_argv0; + + if (execvp (exec_path, argv) == -1) + { + if (setup_finished_pipe[1] != -1) + { + int saved_errno = errno; + char data = 0; + res = write_to_fd (setup_finished_pipe[1], &data, 1); + errno = saved_errno; + /* Ignore res, if e.g. the parent died and closed setup_finished_pipe[0] + we don't want to error out here */ + } + die_with_error ("execvp %s", exec_path); + } + + return 0; +} diff --git a/codex-rs/vendor/bubblewrap/bubblewrap.jpg b/codex-rs/vendor/bubblewrap/bubblewrap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d3c243dface42da83cdec1be9bd93f5475120a9b GIT binary patch literal 40239 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 literal 0 HcmV?d00001 diff --git a/codex-rs/vendor/bubblewrap/bwrap.xml b/codex-rs/vendor/bubblewrap/bwrap.xml new file mode 100644 index 00000000000..f379f0fa46a --- /dev/null +++ b/codex-rs/vendor/bubblewrap/bwrap.xml @@ -0,0 +1,648 @@ + + + + + + bwrap + Containers + + + Developer + Alexander + Larsson + + + Developer + Colin + Walters + + + + + + bwrap + 1 + User Commands + + + + bwrap + container setup utility + + + + +bwrap +OPTION +COMMAND + + + +Description + + bwrap is a unprivileged low-level sandboxing tool + (optionally setuid on older distributions). You + are unlikely to use it directly from the commandline, although that is possible. + + + It works by creating a new, completely empty, filesystem namespace where the root + is on a tmpfs that is invisible from the host, and which will be automatically + cleaned up when the last process exits. You can then use commandline options to + construct the root filesystem and process environment for the command to run in + the namespace. + + + By default, bwrap creates a new mount namespace for the sandbox. + Optionally it also sets up new user, ipc, pid, network and uts namespaces (but note the + user namespace is required if bwrap is not installed setuid root). + The application in the sandbox can be made to run with a different UID and GID. + + + If needed (e.g. when using a PID namespace) bwrap + is running a minimal pid 1 process in the sandbox that is + responsible for reaping zombies. It also detects when the initial + application process (pid 2) dies and reports its exit status back to + the original spawner. The pid 1 process exits to clean up the + sandbox when there are no other processes in the sandbox left. + + + +Options + + When options are used multiple times, the last option wins, unless otherwise + specified. + + General options: + + + + Print help and exit + + + + Print version + + + + + Parse nul-separated arguments from the given file descriptor. + This option can be used multiple times to parse options from + multiple sources. + + + + + Set argv[0] to the value VALUE before running the program + + + + + + Prefix each line of diagnostic output with a numeric severity + level enclosed in angle brackets. + The severity levels used are based on the constants used by + syslog3: + for example, <4> indicates a warning, + because LOG_WARNING has numeric value 4. + Numbers smaller than 4 indicate fatal errors, and numbers larger + than 4 indicate informational messages. + These prefixes can be parsed by tools compatible with + logger --prio-prefix (see + logger1) + or systemd-cat --level-prefix=1 (see + systemd-cat1). + + + + + Options related to kernel namespaces: + + + + Create a new user namespace + + + + Create a new user namespace if possible else skip it + + + + Create a new ipc namespace + + + + Create a new pid namespace + + + + Create a new network namespace + + + + Create a new uts namespace + + + + Create a new cgroup namespace + + + + Create a new cgroup namespace if possible else skip it + + + + Unshare all possible namespaces. Currently equivalent with: + + + + Retain the network namespace, overriding an earlier or + + + + Use an existing user namespace instead of creating a new one. The namespace must fulfil the permission requirements for setns(), which generally means that it must be a descendant of the currently active user namespace, owned by the same user. + This is incompatible with --unshare-user, and doesn't work in the setuid version of bubblewrap. + + + + After setting up the new namespace, switch into the specified namespace. For this to work the specified namespace must be a descendant of the user namespace used for the setup, so this is only useful in combination with --userns. + This is useful because sometimes bubblewrap itself creates nested user namespaces (to work around some kernel issues) and --userns2 can be used to enter these. + + + + + Prevent the process in the sandbox from creating further user namespaces, + so that it cannot rearrange the filesystem namespace or do other more + complex namespace modification. + This is currently implemented by setting the + user.max_user_namespaces sysctl to 1, and then + entering a nested user namespace which is unable to raise that limit + in the outer namespace. + This option requires , and doesn't work + in the setuid version of bubblewrap. + + + + + + Confirm that the process in the sandbox has been prevented from + creating further user namespaces, but without taking any particular + action to prevent that. For example, this can be combined with + to check that the given user namespace + has already been set up to prevent the creation of further user + namespaces. + + + + + Use an existing pid namespace instead of creating one. This is often used with --userns, because the pid namespace must be owned by the same user namespace that bwrap uses. + Note that this can be combined with --unshare-pid, and in that case it means that the sandbox will be in its own pid namespace, which is a child of the passed in one. + + + + Use a custom user id in the sandbox (requires ) + + + + Use a custom group id in the sandbox (requires ) + + + + Use a custom hostname in the sandbox (requires ) + + + Options about environment setup: + + + + Change directory to DIR + + + + Set an environment variable + + + + Unset an environment variable + + + + Unset all environment variables, except for + PWD and any that are subsequently set by + + + + Options for monitoring the sandbox from the outside: + + + + + Take a lock on DEST while the sandbox is running. + This option can be used multiple times to take locks on multiple files. + + + + + Keep this file descriptor open while the sandbox is running + + + + Filesystem related options. These are all operations that modify the filesystem directly, or + mounts stuff in the filesystem. These are applied in the order they are given as arguments. + + + Any missing parent directories that are required to create a specified destination are + automatically created as needed. Their permissions are normally set to 0755 + (rwxr-xr-x). However, if a option is in effect, and + it sets the permissions for group or other to zero, then newly-created + parent directories will also have their corresponding permission set to zero. + modifies the size of the created mount when preceding a + action; and + can be combined. + + + + + This option does nothing on its own, and must be followed + by one of the options that it affects. It sets the permissions + for the next operation to OCTAL. + Subsequent operations are not affected: for example, + --perms 0700 --tmpfs /a --tmpfs /b will mount + /a with permissions 0700, then return to + the default permissions for /b. + Note that and can be + combined: --perms 0700 --size 10485760 --tmpfs /s will apply + permissions as well as a maximum size to the created tmpfs. + + + + This option does nothing on its own, and must be followed + by --tmpfs. It sets the size in bytes for the next tmpfs. + For example, --size 10485760 --tmpfs /tmp will create a tmpfs + at /tmp of size 10MiB. Subsequent operations are not + affected: for example, + --size 10485760 --tmpfs /a --tmpfs /b will mount + /a with size 10MiB, then return to the default size for + /b. + Note that and can be + combined: --size 10485760 --perms 0700 --tmpfs /s will apply + permissions as well as a maximum size to the created tmpfs. + + + + Bind mount the host path SRC on DEST + + + + Equal to but ignores non-existent SRC + + + + Bind mount the host path SRC on DEST, allowing device access + + + + Equal to but ignores non-existent SRC + + + + Bind mount the host path SRC readonly on DEST + + + + Equal to but ignores non-existent SRC + + + + Remount the path DEST as readonly. It works only on the specified mount point, without changing any other mount point under the specified path + + + + + + This option does nothing on its own, and must be followed by one of + the other overlay options. It specifies a host + path from which files should be read if they aren't present in a + higher layer. + + + This option can be used multiple times to provide multiple sources. + The sources are overlaid in the order given, with the first source on + the command line at the bottom of the stack: if a given path to be + read exists in more than one source, the file is read from the last + such source specified. + + + (For readers familiar with overlayfs, note that this is the + reverse of the order used by the kernel's lowerdir + mount option.) + + + + + + + + + + + + + + Use overlayfs to mount the host paths specified by + RWSRC and all immediately preceding + on DEST. + DEST will contain the union of all the files + in all the layers. + + + With --overlay all writes will go to + RWSRC. Reads will come preferentially from + RWSRC, and then from any + paths. + WORKDIR must be an empty directory on the + same filesystem as RWSRC, and is used + internally by the kernel. + + + With --tmp-overlay all writes will go to + the tmpfs that hosts the sandbox root, in a location not accessible + from either the host or the child process. Writes will therefore not + be persisted across multiple runs. + + + With --ro-overlay the filesystem will be + mounted read-only. This option requires at least two + to precede it. + + + None of these options are available in the setuid version of + bubblewrap. Using --ro-overlay or providing + more than one requires a Linux kernel + version of 4.0 or later. + + + Due to limitations of overlayfs, no host directory given via + --overlay-src or + --overlay may be an ancestor of another, + after resolving symlinks. Depending on version, the Linux kernel may + or may not enforce this, but if not then overlayfs's behavior is + undefined. + + + For more information see the Overlay Filesystem documentation in the + Linux kernel at + https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt + + + + + + Mount procfs on DEST + + + + Mount new devtmpfs on DEST + + + + + Mount new tmpfs on DEST. + If the previous option was , it sets the + mode of the tmpfs. Otherwise, the tmpfs has mode 0755. + If the previous option was , it sets the + size in bytes of the tmpfs. Otherwise, the tmpfs has the default size. + + + + + Mount new mqueue on DEST + + + + + Create a directory at DEST. + If the directory already exists, its permissions are unmodified, + ignoring (use + if the permissions of an existing directory need to be changed). + If the directory is newly created and the previous option was + , it sets the mode of the directory. + Otherwise, newly-created directories have mode 0755. + + + + + + Copy from the file descriptor FD to + DEST. + If the previous option was , it sets the + mode of the new file. Otherwise, the file has mode 0666 + (note that this is not the same as ). + + + + + + Copy from the file descriptor FD to + a file which is bind-mounted on DEST. + If the previous option was , it sets the + mode of the new file. Otherwise, the file has mode 0600 + (note that this is not the same as ). + + + + + + Copy from the file descriptor FD to + a file which is bind-mounted read-only on + DEST. + If the previous option was , it sets the + mode of the new file. Otherwise, the file has mode 0600 + (note that this is not the same as ). + + + + + + Create a symlink at DEST with target + SRC. + Since version 0.9.0, it is not considered to be an error if + DEST already exists as a symbolic link and its + target is exactly SRC. + Before version 0.9.0, if DEST already + existed, this would be treated as an error (even if its target + was identical to SRC). + + + + + + + Set the permissions of PATH, which + must already exist, to OCTAL. + + + + + Lockdown options: + + + + + Load and use seccomp rules from FD. + The rules need to be in the form of a compiled cBPF program, + as generated by seccomp_export_bpf. + If this option is given more than once, only the last one is used. + Use if multiple seccomp programs + are needed. + + + + + + Load and use seccomp rules from FD. + The rules need to be in the form of a compiled cBPF program, + as generated by seccomp_export_bpf. + This option can be repeated, in which case all the seccomp + programs will be loaded in the order given (note that the kernel + will evaluate them in reverse order, so the last program on the + bwrap command-line is evaluated first). All of them, except + possibly the last, must allow use of the PR_SET_SECCOMP prctl. + This option cannot be combined with . + + + + + + Exec Label from the sandbox. On an SELinux system you can specify the SELinux + context for the sandbox process(s). + + + + + + File label for temporary sandbox content. On an SELinux system you can specify + the SELinux context for the sandbox content. + + + + + + Block the sandbox on reading from FD until some data is available. + + + + + + Do not initialize the user namespace but wait on FD until it is ready. This allow + external processes (like newuidmap/newgidmap) to setup the user namespace before it + is used by the sandbox process. + + + + + + Write information in JSON format about the sandbox to FD. + + + + + + Multiple JSON documents are written to FD, + one per line ("JSON lines" format). + Each line is a single JSON object. + After bwrap has started the child process inside the sandbox, + it writes an object with a child-pid member to the + (this duplicates the older ). + The corresponding value is the process ID of the child process in the pid namespace from + which bwrap was run. + If available, the namespace IDs are also included in the object with the child-pid; + again, this duplicates the older . + When the child process inside the sandbox exits, bwrap writes an object + with an exit-code member, and then closes the . The value + corresponding to exit-code is the exit status of the child, in the usual + shell encoding (n if it exited normally with status n, or 128+n if it was killed by signal n). + Other members may be added to those objects in future versions of bwrap, + and other JSON objects may be added before or after the current objects, so readers must + ignore members and objects that they do not understand. + + + + + + Create a new terminal session for the sandbox (calls setsid()). This + disconnects the sandbox from the controlling terminal which means + the sandbox can't for instance inject input into the terminal. + + Note: In a general sandbox, if you don't use --new-session, it is + recommended to use seccomp to disallow the TIOCSTI ioctl, otherwise + the application can feed keyboard input to the terminal + which can e.g. lead to out-of-sandbox command execution + (see CVE-2017-5226). + + + + + + Ensures child process (COMMAND) dies when bwrap's parent dies. Kills (SIGKILL) + all bwrap sandbox processes in sequence from parent to child + including COMMAND process when bwrap or bwrap's parent dies. + See prctl, PR_SET_PDEATHSIG. + + + + + + Do not create a process with PID=1 in the sandbox to reap child processes. + + + + + + Add the specified capability CAP, e.g. + CAP_DAC_READ_SEARCH, when running as privileged user. It accepts + the special value ALL to add all the permitted caps. + + + + + + Drop the specified capability when running as privileged user. It accepts + the special value ALL to drop all the caps. + + By default no caps are left in the sandboxed process. The + and + options are processed in the order they are specified on the + command line. Please be careful to the order they are specified. + + + + + + + Environment + + + + HOME + + Used as the cwd in the sandbox if has not been + explicitly specified and the current cwd is not present inside the sandbox. + The option can be used to override the value + that is used here. + + + + + + + Exit status + + + The bwrap command returns the exit status of the + initial application process (pid 2 in the sandbox). + + + + diff --git a/codex-rs/vendor/bubblewrap/ci/builddeps.sh b/codex-rs/vendor/bubblewrap/ci/builddeps.sh new file mode 100755 index 00000000000..5bca51e255d --- /dev/null +++ b/codex-rs/vendor/bubblewrap/ci/builddeps.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Copyright 2021 Simon McVittie +# SPDX-License-Identifier: LGPL-2.0-or-later + +set -eux +set -o pipefail + +usage() { + if [ "${1-2}" -ne 0 ]; then + exec >&2 + fi + cat <&2 + usage 2 + ;; + esac +done + +# No more arguments please +for arg in "$@"; do + usage 2 +done + +if dpkg-vendor --derives-from Debian; then + apt-get -y update + apt-get -q -y install \ + build-essential \ + docbook-xml \ + docbook-xsl \ + libcap-dev \ + libselinux1-dev \ + libtool \ + meson \ + pkg-config \ + python3 \ + xsltproc \ + ${NULL+} + + if [ -n "${opt_clang}" ]; then + apt-get -y install clang + fi + + exit 0 +fi + +if command -v yum; then + yum -y install \ + 'pkgconfig(libselinux)' \ + /usr/bin/eu-readelf \ + docbook-style-xsl \ + gcc \ + git \ + libasan \ + libcap-devel \ + libtool \ + libtsan \ + libubsan \ + libxslt \ + make \ + meson \ + redhat-rpm-config \ + rsync \ + ${NULL+} + + if [ -n "${opt_clang}" ]; then + yum -y install clang + fi + + exit 0 +fi + +echo "Unknown distribution" >&2 +exit 1 + +# vim:set sw=4 sts=4 et: diff --git a/codex-rs/vendor/bubblewrap/ci/enable-userns.sh b/codex-rs/vendor/bubblewrap/ci/enable-userns.sh new file mode 100755 index 00000000000..9310e558b5a --- /dev/null +++ b/codex-rs/vendor/bubblewrap/ci/enable-userns.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +echo "kernel.apparmor_restrict_unprivileged_userns = 0" > /etc/sysctl.d/99-userns.conf +sysctl --system diff --git a/codex-rs/vendor/bubblewrap/completions/bash/bwrap b/codex-rs/vendor/bubblewrap/completions/bash/bwrap new file mode 100644 index 00000000000..e7a523c2850 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/completions/bash/bwrap @@ -0,0 +1,80 @@ +# shellcheck shell=bash + +# bash completion file for bubblewrap commands +# + +_bwrap() { + local cur prev words cword + _init_completion || return + + # Please keep sorted in LC_ALL=C order + local boolean_options=" + --as-pid-1 + --assert-userns-disabled + --clearenv + --disable-userns + --help + --new-session + --unshare-all + --unshare-cgroup + --unshare-cgroup-try + --unshare-ipc + --unshare-net + --unshare-pid + --unshare-user + --unshare-user-try + --unshare-uts + --version + " + + # Please keep sorted in LC_ALL=C order + local options_with_args=" + $boolean_optons + --add-seccomp-fd + --args + --argv0 + --bind + --bind-data + --block-fd + --cap-add + --cap-drop + --chdir + --chmod + --dev + --dev-bind + --die-with-parent + --dir + --exec-label + --file + --file-label + --gid + --hostname + --info-fd + --lock-file + --overlay + --overlay-src + --perms + --proc + --remount-ro + --ro-bind + --ro-overlay + --seccomp + --setenv + --size + --symlink + --sync-fd + --tmp-overlay + --uid + --unsetenv + --userns-block-fd + " + + if [[ "$cur" == -* ]]; then + COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) + fi + + return 0 +} +complete -F _bwrap bwrap + +# vim:set ft=bash: diff --git a/codex-rs/vendor/bubblewrap/completions/bash/meson.build b/codex-rs/vendor/bubblewrap/completions/bash/meson.build new file mode 100644 index 00000000000..323ad3973bc --- /dev/null +++ b/codex-rs/vendor/bubblewrap/completions/bash/meson.build @@ -0,0 +1,35 @@ +bash_completion_dir = get_option('bash_completion_dir') + +if bash_completion_dir == '' + bash_completion = dependency( + 'bash-completion', + version : '>=2.0', + required : false, + ) + + if bash_completion.found() + if meson.version().version_compare('>=0.51.0') + bash_completion_dir = bash_completion.get_variable( + default_value: '', + pkgconfig: 'completionsdir', + pkgconfig_define: [ + 'datadir', get_option('prefix') / get_option('datadir'), + ], + ) + else + bash_completion_dir = bash_completion.get_pkgconfig_variable( + 'completionsdir', + default: '', + define_variable: [ + 'datadir', get_option('prefix') / get_option('datadir'), + ], + ) + endif + endif +endif + +if bash_completion_dir == '' + bash_completion_dir = get_option('datadir') / 'bash-completion' / 'completions' +endif + +install_data('bwrap', install_dir : bash_completion_dir) diff --git a/codex-rs/vendor/bubblewrap/completions/meson.build b/codex-rs/vendor/bubblewrap/completions/meson.build new file mode 100644 index 00000000000..958c90a2ebb --- /dev/null +++ b/codex-rs/vendor/bubblewrap/completions/meson.build @@ -0,0 +1,7 @@ +if get_option('bash_completion').enabled() + subdir('bash') +endif + +if get_option('zsh_completion').enabled() + subdir('zsh') +endif diff --git a/codex-rs/vendor/bubblewrap/completions/zsh/_bwrap b/codex-rs/vendor/bubblewrap/completions/zsh/_bwrap new file mode 100644 index 00000000000..fbddda435e4 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/completions/zsh/_bwrap @@ -0,0 +1,115 @@ +#compdef bwrap + +_bwrap_args_after_perms_size=( + # Please sort alphabetically (in LC_ALL=C order) by option name + '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/' +) + +_bwrap_args_after_perms=( + # Please sort alphabetically (in LC_ALL=C order) by option name + '--bind-data[Copy from FD to file which is bind-mounted on DEST]: :_guard "[0-9]#" "file descriptor to read content":destination:_files' + '--dir[Create dir at DEST]:directory to create:_files -/' + '--file[Copy from FD to destination DEST]: :_guard "[0-9]#" "file descriptor to read content from":destination:_files' + '--ro-bind-data[Copy from FD to file which is readonly bind-mounted on DEST]: :_guard "[0-9]#" "file descriptor to read content from":destination:_files' + '--size[Set size in bytes for next action argument]: :->after_perms_size' + '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/' +) + +_bwrap_args_after_size=( + # Please sort alphabetically (in LC_ALL=C order) by option name + '--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms_size' + '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/' +) + +_bwrap_args=( + '*::arguments:_normal' + $_bwrap_args_after_perms + + # Please sort alphabetically (in LC_ALL=C order) by option name + '--add-seccomp-fd[Load and use seccomp rules from FD]: :_guard "[0-9]#" "file descriptor to read seccomp rules from"' + '--assert-userns-disabled[Fail unless further use of user namespace inside sandbox is disabled]' + '--args[Parse NUL-separated args from FD]: :_guard "[0-9]#" "file descriptor with NUL-separated arguments"' + '--argv0[Set argv0 to the value VALUE before running the program]:value:' + '--as-pid-1[Do not install a reaper process with PID=1]' + '--bind-try[Equal to --bind but ignores non-existent SRC]:source:_files:destination:_files' + '--bind[Bind mount the host path SRC on DEST]:source:_files:destination:_files' + '--block-fd[Block on FD until some data to read is available]: :_guard "[0-9]#" "file descriptor to block on"' + '--cap-add[Add cap CAP when running as privileged user]:capability to add:->caps' + '--cap-drop[Drop cap CAP when running as privileged user]:capability to add:->caps' + '--chdir[Change directory to DIR]:working directory for sandbox: _files -/' + '--chmod[Set permissions]: :_guard "[0-7]#" "permissions in octal":path to set permissions:_files' + '--clearenv[Unset all environment variables]' + '--dev-bind-try[Equal to --dev-bind but ignores non-existent SRC]:source:_files:destination:_files' + '--dev-bind[Bind mount the host path SRC on DEST, allowing device access]:source:_files:destination:_files' + '--dev[Mount new dev on DEST]:mount point for /dev:_files -/' + "--die-with-parent[Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.]" + '--disable-userns[Disable further use of user namespaces inside sandbox]' + '--exec-label[Exec label for the sandbox]:SELinux label:_selinux_contexts' + '--file-label[File label for temporary sandbox content]:SELinux label:_selinux_contexts' + '--gid[Custom gid in the sandbox (requires --unshare-user or --userns)]: :_guard "[0-9]#" "numeric group ID"' + '--help[Print help and exit]' + '--hostname[Custom hostname in the sandbox (requires --unshare-uts)]:hostname:' + '--info-fd[Write information about the running container to FD]: :_guard "[0-9]#" "file descriptor to write to"' + '--json-status-fd[Write container status to FD as multiple JSON documents]: :_guard "[0-9]#" "file descriptor to write to"' + '--lock-file[Take a lock on DEST while sandbox is running]:lock file:_files' + '--mqueue[Mount new mqueue on DEST]:mount point for mqueue:_files -/' + '--new-session[Create a new terminal session]' + '--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms' + '--pidns[Use this user namespace (as parent namespace if using --unshare-pid)]: :' + '--proc[Mount new procfs on DEST]:mount point for procfs:_files -/' + '--remount-ro[Remount DEST as readonly; does not recursively remount]:mount point to remount read-only:_files' + '--ro-bind-try[Equal to --ro-bind but ignores non-existent SRC]:source:_files:destination:_files' + '--ro-bind[Bind mount the host path SRC readonly on DEST]:source:_files:destination:_files' + '--seccomp[Load and use seccomp rules from FD]: :_guard "[0-9]#" "file descriptor to read seccomp rules from"' + '--setenv[Set an environment variable]:variable to set:_parameters -g "*export*":value of variable: :' + '--size[Set size in bytes for next action argument]: :->after_size' + '--symlink[Create symlink at DEST with target SRC]:symlink target:_files:symlink to create:_files:' + '--sync-fd[Keep this fd open while sandbox is running]: :_guard "[0-9]#" "file descriptor to keep open"' + '--uid[Custom uid in the sandbox (requires --unshare-user or --userns)]: :_guard "[0-9]#" "numeric group ID"' + '(--clearenv)--unsetenv[Unset an environment variable]:variable to unset:_parameters -g "*export*"' + '--unshare-all[Unshare every namespace we support by default]' + '--unshare-cgroup-try[Create new cgroup namespace if possible else continue by skipping it]' + '--unshare-cgroup[Create new cgroup namespace]' + '--unshare-ipc[Create new ipc namespace]' + '--unshare-net[Create new network namespace]' + '--unshare-pid[Create new pid namespace]' + '(--userns --userns2)--unshare-user[Create new user namespace (may be automatically implied if not setuid)]' + '--unshare-user-try[Create new user namespace if possible else continue by skipping it]' + '--unshare-uts[Create new uts namespace]' + '(--unshare-user)--userns[Use this user namespace (cannot combine with --unshare-user)]: :' + '--userns-block-fd[Block on FD until the user namespace is ready]: :_guard "[0-9]#" "file descriptor to block on"' + '(--unshare-user)--userns2[After setup switch to this user namespace, only useful with --userns]: :' + '--version[Print version]' +) + +_bwrap() { + _arguments -S $_bwrap_args + case "$state" in + after_perms) + _values -S ' ' 'option' $_bwrap_args_after_perms + ;; + + after_size) + _values -S ' ' 'option' $_bwrap_args_after_size + ;; + + after_perms_size) + _values -S ' ' 'option' $_bwrap_args_after_perms_size + ;; + + caps) + # $ grep -E '#define\sCAP_\w+\s+[0-9]+' /usr/include/linux/capability.h | awk '{print $2}' | xargs echo + local all_caps=( + CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_FSETID \ + CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_LINUX_IMMUTABLE \ + CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_ADMIN CAP_NET_RAW \ + CAP_IPC_LOCK CAP_IPC_OWNER CAP_SYS_MODULE CAP_SYS_RAWIO CAP_SYS_CHROOT \ + CAP_SYS_PTRACE CAP_SYS_PACCT CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_NICE \ + CAP_SYS_RESOURCE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_MKNOD CAP_LEASE \ + CAP_AUDIT_WRITE CAP_AUDIT_CONTROL CAP_SETFCAP CAP_MAC_OVERRIDE \ + CAP_MAC_ADMIN CAP_SYSLOG CAP_WAKE_ALARM CAP_BLOCK_SUSPEND CAP_AUDIT_READ + ) + _values 'caps' $all_caps + ;; + esac +} diff --git a/codex-rs/vendor/bubblewrap/completions/zsh/meson.build b/codex-rs/vendor/bubblewrap/completions/zsh/meson.build new file mode 100644 index 00000000000..7bda727c77d --- /dev/null +++ b/codex-rs/vendor/bubblewrap/completions/zsh/meson.build @@ -0,0 +1,7 @@ +zsh_completion_dir = get_option('zsh_completion_dir') + +if zsh_completion_dir == '' + zsh_completion_dir = get_option('datadir') / 'zsh' / 'site-functions' +endif + +install_data('_bwrap', install_dir : zsh_completion_dir) diff --git a/codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh b/codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh new file mode 100755 index 00000000000..1cdb788f383 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Use bubblewrap to run /bin/sh reusing the host OS binaries (/usr), but with +# separate /tmp, /home, /var, /run, and /etc. For /etc we just inherit the +# host's resolv.conf, and set up "stub" passwd/group files. Not sharing +# /home for example is intentional. If you wanted to, you could design +# a bwrap-using program that shared individual parts of /home, perhaps +# public content. +# +# Another way to build on this example is to remove --share-net to disable +# networking. +set -euo pipefail +(exec bwrap --ro-bind /usr /usr \ + --dir /tmp \ + --dir /var \ + --symlink ../tmp var/tmp \ + --proc /proc \ + --dev /dev \ + --ro-bind /etc/resolv.conf /etc/resolv.conf \ + --symlink usr/lib /lib \ + --symlink usr/lib64 /lib64 \ + --symlink usr/bin /bin \ + --symlink usr/sbin /sbin \ + --chdir / \ + --unshare-all \ + --share-net \ + --die-with-parent \ + --dir /run/user/$(id -u) \ + --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ + --setenv PS1 "bwrap-demo$ " \ + --file 11 /etc/passwd \ + --file 12 /etc/group \ + /bin/sh) \ + 11< <(getent passwd $UID 65534) \ + 12< <(getent group $(id -g) 65534) diff --git a/codex-rs/vendor/bubblewrap/demos/flatpak-run.sh b/codex-rs/vendor/bubblewrap/demos/flatpak-run.sh new file mode 100755 index 00000000000..91493104403 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/demos/flatpak-run.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# For this to work you first have to run these commands: +# curl -O http://sdk.gnome.org/nightly/keys/nightly.gpg +# flatpak --user remote-add --gpg-key=nightly.gpg gnome-nightly http://sdk.gnome.org/nightly/repo/ +# flatpak --user install gnome-nightly org.gnome.Platform +# flatpak --user install gnome-nightly org.gnome.Weather + +mkdir -p ~/.var/app/org.gnome.Weather/cache ~/.var/app/org.gnome.Weather/config ~/.var/app/org.gnome.Weather/data + +( + exec bwrap \ + --ro-bind ~/.local/share/flatpak/runtime/org.gnome.Platform/x86_64/master/active/files /usr \ + --lock-file /usr/.ref \ + --ro-bind ~/.local/share/flatpak/app/org.gnome.Weather/x86_64/master/active/files/ /app \ + --lock-file /app/.ref \ + --dev /dev \ + --proc /proc \ + --dir /tmp \ + --symlink /tmp /var/tmp \ + --symlink /run /var/run \ + --symlink usr/lib /lib \ + --symlink usr/lib64 /lib64 \ + --symlink usr/bin /bin \ + --symlink usr/sbin /sbin \ + --symlink usr/etc /etc \ + --dir /run/user/`id -u` \ + --ro-bind /etc/machine-id /usr/etc/machine-id \ + --ro-bind /etc/resolv.conf /run/host/monitor/resolv.conf \ + --ro-bind /sys/block /sys/block \ + --ro-bind /sys/bus /sys/bus \ + --ro-bind /sys/class /sys/class \ + --ro-bind /sys/dev /sys/dev \ + --ro-bind /sys/devices /sys/devices \ + --dev-bind /dev/dri /dev/dri \ + --bind /tmp/.X11-unix/X0 /tmp/.X11-unix/X99 \ + --bind ~/.var/app/org.gnome.Weather ~/.var/app/org.gnome.Weather \ + --bind ~/.config/dconf ~/.config/dconf \ + --bind /run/user/`id -u`/dconf /run/user/`id -u`/dconf \ + --unshare-pid \ + --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \ + --setenv DISPLAY :99 \ + --setenv GI_TYPELIB_PATH /app/lib/girepository-1.0 \ + --setenv GST_PLUGIN_PATH /app/lib/gstreamer-1.0 \ + --setenv LD_LIBRARY_PATH /app/lib:/usr/lib/GL \ + --setenv DCONF_USER_CONFIG_DIR .config/dconf \ + --setenv PATH /app/bin:/usr/bin \ + --setenv XDG_CONFIG_DIRS /app/etc/xdg:/etc/xdg \ + --setenv XDG_DATA_DIRS /app/share:/usr/share \ + --setenv SHELL /bin/sh \ + --setenv XDG_CACHE_HOME ~/.var/app/org.gnome.Weather/cache \ + --setenv XDG_CONFIG_HOME ~/.var/app/org.gnome.Weather/config \ + --setenv XDG_DATA_HOME ~/.var/app/org.gnome.Weather/data \ + --file 10 /run/user/`id -u`/flatpak-info \ + --bind-data 11 /usr/etc/passwd \ + --bind-data 12 /usr/etc/group \ + --seccomp 13 \ + /bin/sh) \ + 11< <(getent passwd $UID 65534 ) \ + 12< <(getent group $(id -g) 65534) \ + 13< `dirname $0`/flatpak.bpf \ + 10<h*Q_+j7k z)4t=_ZgFDY^4o6m&u(yL-*9f%p%gi<$?Yo|_9bomB653)=aDC$MV?%XJh>Wq@@eGB zCy^(6ktZKVp6o`R>_nb?6nXMt)bOv}VVWy*3#^B4PUNx4w#q$wg^sPTWywBkf1!%^qkAdqw4Y&DPoO^AS6`@N We)v7|M64o@XNCT^u%z{a9{vNd1~

=0.49.0', + default_options : [ + 'warning_level=2', + ], +) + +cc = meson.get_compiler('c') +add_project_arguments('-D_GNU_SOURCE', language : 'c') +common_include_directories = include_directories('.') + +# Keep this in sync with ostree, except remove -Wall (part of Meson +# warning_level 2) and -Werror=declaration-after-statement +add_project_arguments( + cc.get_supported_arguments([ + '-Werror=shadow', + '-Werror=empty-body', + '-Werror=strict-prototypes', + '-Werror=missing-prototypes', + '-Werror=implicit-function-declaration', + '-Werror=pointer-arith', + '-Werror=init-self', + '-Werror=missing-declarations', + '-Werror=return-type', + '-Werror=overflow', + '-Werror=int-conversion', + '-Werror=parenthesis', + '-Werror=incompatible-pointer-types', + '-Werror=misleading-indentation', + '-Werror=missing-include-dirs', + '-Werror=aggregate-return', + + # Extra warnings specific to bubblewrap + '-Werror=switch-default', + '-Wswitch-enum', + + # Deliberately not warning about these, ability to zero-initialize + # a struct is a feature + '-Wno-missing-field-initializers', + '-Wno-error=missing-field-initializers', + ]), + language : 'c', +) + +if ( + cc.has_argument('-Werror=format=2') + and cc.has_argument('-Werror=format-security') + and cc.has_argument('-Werror=format-nonliteral') +) + add_project_arguments([ + '-Werror=format=2', + '-Werror=format-security', + '-Werror=format-nonliteral', + ], language : 'c') +endif + +bash = find_program('bash', required : false) + +if get_option('python') == '' + python = find_program('python3') +else + python = find_program(get_option('python')) +endif + +libcap_dep = dependency('libcap', required : true) + +selinux_dep = dependency( + 'libselinux', + version : '>=2.1.9', + # if disabled, Meson will behave as though libselinux was not found + required : get_option('selinux'), +) + +cdata = configuration_data() +cdata.set_quoted( + 'PACKAGE_STRING', + '@0@ @1@'.format(meson.project_name(), meson.project_version()), +) + +if selinux_dep.found() + cdata.set('HAVE_SELINUX', 1) + if selinux_dep.version().version_compare('>=2.3') + cdata.set('HAVE_SELINUX_2_3', 1) + endif +endif + +if get_option('require_userns') + cdata.set('ENABLE_REQUIRE_USERNS', 1) +endif + +configure_file( + output : 'config.h', + configuration : cdata, +) + +if meson.is_subproject() and get_option('program_prefix') == '' + error('program_prefix option must be set when bwrap is a subproject') +endif + +if get_option('bwrapdir') != '' + bwrapdir = get_option('bwrapdir') +elif meson.is_subproject() + bwrapdir = get_option('libexecdir') +else + bwrapdir = get_option('bindir') +endif + +bwrap = executable( + get_option('program_prefix') + 'bwrap', + [ + 'bubblewrap.c', + 'bind-mount.c', + 'network.c', + 'utils.c', + ], + build_rpath : get_option('build_rpath'), + install : true, + install_dir : bwrapdir, + install_rpath : get_option('install_rpath'), + dependencies : [selinux_dep, libcap_dep], +) + +manpages_xsl = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl' +xsltproc = find_program('xsltproc', required : get_option('man')) +build_man_page = false + +if xsltproc.found() and not meson.is_subproject() + if run_command([ + xsltproc, '--nonet', manpages_xsl, + ], check : false).returncode() == 0 + message('Docbook XSL found, man page enabled') + build_man_page = true + elif get_option('man').enabled() + error('Man page requested, but Docbook XSL stylesheets not found') + else + message('Docbook XSL not found, man page disabled automatically') + endif +endif + +if build_man_page + custom_target( + 'bwrap.1', + output : 'bwrap.1', + input : 'bwrap.xml', + command : [ + xsltproc, + '--nonet', + '--stringparam', 'man.output.quietly', '1', + '--stringparam', 'funcsynopsis.style', 'ansi', + '--stringparam', 'man.th.extra1.suppress', '1', + '--stringparam', 'man.authors.section.enabled', '0', + '--stringparam', 'man.copyright.section.enabled', '0', + '-o', '@OUTPUT@', + manpages_xsl, + '@INPUT@', + ], + install : true, + install_dir : get_option('mandir') / 'man1', + ) +endif + +if not meson.is_subproject() + subdir('completions') +endif + +if get_option('tests') + subdir('tests') +endif diff --git a/codex-rs/vendor/bubblewrap/meson_options.txt b/codex-rs/vendor/bubblewrap/meson_options.txt new file mode 100644 index 00000000000..5e25ee86f86 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/meson_options.txt @@ -0,0 +1,73 @@ +option( + 'bash_completion', + type : 'feature', + description : 'install bash completion script', + value : 'enabled', +) +option( + 'bash_completion_dir', + type : 'string', + description : 'install bash completion script in this directory', + value : '', +) +option( + 'bwrapdir', + type : 'string', + description : 'install bwrap in this directory [default: bindir, or libexecdir in subprojects]', +) +option( + 'build_rpath', + type : 'string', + description : 'set a RUNPATH or RPATH on the bwrap executable', +) +option( + 'install_rpath', + type : 'string', + description : 'set a RUNPATH or RPATH on the bwrap executable', +) +option( + 'man', + type : 'feature', + description : 'generate man pages', + value : 'auto', +) +option( + 'program_prefix', + type : 'string', + description : 'Prepend string to bwrap executable name, for use with subprojects', +) +option( + 'python', + type : 'string', + description : 'Path to Python 3, or empty to use python3', +) +option( + 'require_userns', + type : 'boolean', + description : 'require user namespaces by default when installed setuid', + value : false, +) +option( + 'selinux', + type : 'feature', + description : 'enable optional SELINUX support', + value : 'auto', +) +option( + 'tests', + type : 'boolean', + description : 'build tests', + value : true, +) +option( + 'zsh_completion', + type : 'feature', + description : 'install zsh completion script', + value : 'enabled', +) +option( + 'zsh_completion_dir', + type : 'string', + description : 'install zsh completion script in this directory', + value : '', +) diff --git a/codex-rs/vendor/bubblewrap/network.c b/codex-rs/vendor/bubblewrap/network.c new file mode 100644 index 00000000000..373d606a0ab --- /dev/null +++ b/codex-rs/vendor/bubblewrap/network.c @@ -0,0 +1,199 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "network.h" + +static void * +add_rta (struct nlmsghdr *header, + int type, + size_t size) +{ + struct rtattr *rta; + size_t rta_size = RTA_LENGTH (size); + + rta = (struct rtattr *) ((char *) header + NLMSG_ALIGN (header->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = rta_size; + + header->nlmsg_len = NLMSG_ALIGN (header->nlmsg_len) + rta_size; + + return RTA_DATA (rta); +} + +static int +rtnl_send_request (int rtnl_fd, + struct nlmsghdr *header) +{ + struct sockaddr_nl dst_addr = { AF_NETLINK, 0 }; + ssize_t sent; + + sent = TEMP_FAILURE_RETRY (sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0, + (struct sockaddr *) &dst_addr, sizeof (dst_addr))); + if (sent < 0) + return -1; + + return 0; +} + +static int +rtnl_read_reply (int rtnl_fd, + unsigned int seq_nr) +{ + char buffer[1024]; + ssize_t received; + struct nlmsghdr *rheader; + + while (1) + { + received = TEMP_FAILURE_RETRY (recv (rtnl_fd, buffer, sizeof (buffer), 0)); + if (received < 0) + return -1; + + rheader = (struct nlmsghdr *) buffer; + while (received >= NLMSG_HDRLEN) + { + if (rheader->nlmsg_seq != seq_nr) + return -1; + if ((pid_t)rheader->nlmsg_pid != getpid ()) + return -1; + if (rheader->nlmsg_type == NLMSG_ERROR) + { + uint32_t *err = NLMSG_DATA (rheader); + if (*err == 0) + return 0; + + return -1; + } + if (rheader->nlmsg_type == NLMSG_DONE) + return 0; + + rheader = NLMSG_NEXT (rheader, received); + } + } +} + +static int +rtnl_do_request (int rtnl_fd, + struct nlmsghdr *header) +{ + if (rtnl_send_request (rtnl_fd, header) != 0) + return -1; + + if (rtnl_read_reply (rtnl_fd, header->nlmsg_seq) != 0) + return -1; + + return 0; +} + +static struct nlmsghdr * +rtnl_setup_request (char *buffer, + int type, + int flags, + size_t size) +{ + struct nlmsghdr *header; + size_t len = NLMSG_LENGTH (size); + static uint32_t counter = 0; + + memset (buffer, 0, len); + + header = (struct nlmsghdr *) buffer; + header->nlmsg_len = len; + header->nlmsg_type = type; + header->nlmsg_flags = flags | NLM_F_REQUEST; + header->nlmsg_seq = counter++; + header->nlmsg_pid = getpid (); + + return header; +} + +void +loopback_setup (void) +{ + int r, if_loopback; + cleanup_fd int rtnl_fd = -1; + char buffer[1024]; + struct sockaddr_nl src_addr = { AF_NETLINK, 0 }; + struct nlmsghdr *header; + struct ifaddrmsg *addmsg; + struct ifinfomsg *infomsg; + struct in_addr *ip_addr; + + src_addr.nl_pid = getpid (); + + if_loopback = (int) if_nametoindex ("lo"); + if (if_loopback <= 0) + die_with_error ("loopback: Failed to look up lo"); + + rtnl_fd = socket (PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (rtnl_fd < 0) + die_with_error ("loopback: Failed to create NETLINK_ROUTE socket"); + + r = bind (rtnl_fd, (struct sockaddr *) &src_addr, sizeof (src_addr)); + if (r < 0) + die_with_error ("loopback: Failed to bind NETLINK_ROUTE socket"); + + header = rtnl_setup_request (buffer, RTM_NEWADDR, + NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, + sizeof (struct ifaddrmsg)); + addmsg = NLMSG_DATA (header); + + addmsg->ifa_family = AF_INET; + addmsg->ifa_prefixlen = 8; + addmsg->ifa_flags = IFA_F_PERMANENT; + addmsg->ifa_scope = RT_SCOPE_HOST; + addmsg->ifa_index = if_loopback; + + ip_addr = add_rta (header, IFA_LOCAL, sizeof (*ip_addr)); + ip_addr->s_addr = htonl (INADDR_LOOPBACK); + + ip_addr = add_rta (header, IFA_ADDRESS, sizeof (*ip_addr)); + ip_addr->s_addr = htonl (INADDR_LOOPBACK); + + assert (header->nlmsg_len < sizeof (buffer)); + + if (rtnl_do_request (rtnl_fd, header) != 0) + die_with_error ("loopback: Failed RTM_NEWADDR"); + + header = rtnl_setup_request (buffer, RTM_NEWLINK, + NLM_F_ACK, + sizeof (struct ifinfomsg)); + infomsg = NLMSG_DATA (header); + + infomsg->ifi_family = AF_UNSPEC; + infomsg->ifi_type = 0; + infomsg->ifi_index = if_loopback; + infomsg->ifi_flags = IFF_UP; + infomsg->ifi_change = IFF_UP; + + assert (header->nlmsg_len < sizeof (buffer)); + + if (rtnl_do_request (rtnl_fd, header) != 0) + die_with_error ("loopback: Failed RTM_NEWLINK"); +} diff --git a/codex-rs/vendor/bubblewrap/network.h b/codex-rs/vendor/bubblewrap/network.h new file mode 100644 index 00000000000..3346b496606 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/network.h @@ -0,0 +1,22 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#pragma once + +void loopback_setup (void); diff --git a/codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec b/codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec new file mode 100644 index 00000000000..0a8001fe472 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec @@ -0,0 +1,48 @@ +%global commit0 66d12bb23b04e201c5846e325f0b10930ed802f8 +%global shortcommit0 %(c=%{commit0}; echo ${c:0:7}) + +Summary: Core execution tool for unprivileged containers +Name: bubblewrap +Version: 0 +Release: 1%{?dist} +#VCS: git:https://github.com/projectatomic/bubblewrap +Source0: https://github.com/projectatomic/%{name}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz +License: LGPLv2+ +URL: https://github.com/projectatomic/bubblewrap + +BuildRequires: git +# We always run autogen.sh +BuildRequires: autoconf automake libtool +BuildRequires: libcap-devel +BuildRequires: pkgconfig(libselinux) +BuildRequires: libxslt +BuildRequires: docbook-style-xsl + +%description +Bubblewrap (/usr/bin/bwrap) is a core execution engine for unprivileged +containers that works as a setuid binary on kernels without +user namespaces. + +%prep +%autosetup -Sgit -n %{name}-%{version} + +%build +env NOCONFIGURE=1 ./autogen.sh +%configure --disable-silent-rules --with-priv-mode=none + +make %{?_smp_mflags} + +%install +make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c" +find $RPM_BUILD_ROOT -name '*.la' -delete + +%files +%license COPYING +%doc README.md +%{_datadir}/bash-completion/completions/bwrap +%if (0%{?rhel} != 0 && 0%{?rhel} <= 7) +%attr(4755,root,root) %{_bindir}/bwrap +%else +%{_bindir}/bwrap +%endif +%{_mandir}/man1/* diff --git a/codex-rs/vendor/bubblewrap/release-checklist.md b/codex-rs/vendor/bubblewrap/release-checklist.md new file mode 100644 index 00000000000..5b2119c8af2 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/release-checklist.md @@ -0,0 +1,18 @@ +bubblewrap release checklist +============================ + +* Collect release notes in `NEWS` +* Update version number in `meson.build` and release date in `NEWS` +* Commit the changes +* `meson dist -C ${builddir}` +* Do any final smoke-testing, e.g. update a package, install and test it +* `git evtag sign v$VERSION` + * Include the release notes from `NEWS` in the tag message +* `git push --atomic origin main v$VERSION` +* https://github.com/containers/bubblewrap/releases/new + * Fill in the new version's tag in the "Tag version" box + * Title: `$VERSION` + * Copy the release notes into the description + * Upload the tarball that you built with `meson dist` + * Get the `sha256sum` of the tarball and append it to the description + * `Publish release` diff --git a/codex-rs/vendor/bubblewrap/tests/libtest-core.sh b/codex-rs/vendor/bubblewrap/tests/libtest-core.sh new file mode 100644 index 00000000000..11df3a3749d --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/libtest-core.sh @@ -0,0 +1,190 @@ +# Core source library for shell script tests; the +# canonical version lives in: +# +# https://github.com/ostreedev/ostree +# +# Known copies are in the following repos: +# +# - https://github.com/containers/bubblewrap +# - https://github.com/coreos/rpm-ostree +# +# Copyright (C) 2017 Colin Walters +# +# SPDX-License-Identifier: LGPL-2.0-or-later +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +fatal() { + echo $@ 1>&2; exit 1 +} +# fatal() is shorter to type, but retain this alias +assert_not_reached () { + fatal "$@" +} + +# Some tests look for specific English strings. Use a UTF-8 version +# of the C (POSIX) locale if we have one, or fall back to en_US.UTF-8 +# (https://sourceware.org/glibc/wiki/Proposals/C.UTF-8) +# +# If we can't find the locale command assume we have support for C.UTF-8 +# (e.g. musl based systems) +if type -p locale >/dev/null; then + export LC_ALL=$(locale -a | grep -iEe '^(C|en_US)\.(UTF-8|utf8)$' | head -n1 || true) + if [ -z "${LC_ALL}" ]; then fatal "Can't find suitable UTF-8 locale"; fi +else + export LC_ALL=C.UTF-8 +fi +# A GNU extension, used whenever LC_ALL is not C +unset LANGUAGE + +# This should really be the default IMO +export G_DEBUG=fatal-warnings + +assert_streq () { + test "$1" = "$2" || fatal "$1 != $2" +} + +assert_str_match () { + if ! echo "$1" | grep -E -q "$2"; then + fatal "$1 does not match regexp $2" + fi +} + +assert_not_streq () { + (! test "$1" = "$2") || fatal "$1 == $2" +} + +assert_has_file () { + test -f "$1" || fatal "Couldn't find '$1'" +} + +assert_has_dir () { + test -d "$1" || fatal "Couldn't find '$1'" +} + +# Dump ls -al + file contents to stderr, then fatal() +_fatal_print_file() { + file="$1" + shift + ls -al "$file" >&2 + sed -e 's/^/# /' < "$file" >&2 + fatal "$@" +} + +_fatal_print_files() { + file1="$1" + shift + file2="$1" + shift + ls -al "$file1" >&2 + sed -e 's/^/# /' < "$file1" >&2 + ls -al "$file2" >&2 + sed -e 's/^/# /' < "$file2" >&2 + fatal "$@" +} + +assert_not_has_file () { + if test -f "$1"; then + _fatal_print_file "$1" "File '$1' exists" + fi +} + +assert_not_file_has_content () { + fpath=$1 + shift + for re in "$@"; do + if grep -q -e "$re" "$fpath"; then + _fatal_print_file "$fpath" "File '$fpath' matches regexp '$re'" + fi + done +} + +assert_not_has_dir () { + if test -d "$1"; then + fatal "Directory '$1' exists" + fi +} + +assert_file_has_content () { + fpath=$1 + shift + for re in "$@"; do + if ! grep -q -e "$re" "$fpath"; then + _fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re'" + fi + done +} + +assert_file_has_content_once () { + fpath=$1 + shift + for re in "$@"; do + if ! test $(grep -e "$re" "$fpath" | wc -l) = "1"; then + _fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re' exactly once" + fi + done +} + +assert_file_has_content_literal () { + fpath=$1; shift + for s in "$@"; do + if ! grep -q -F -e "$s" "$fpath"; then + _fatal_print_file "$fpath" "File '$fpath' doesn't match fixed string list '$s'" + fi + done +} + +assert_file_has_mode () { + mode=$(stat -c '%a' $1) + if [ "$mode" != "$2" ]; then + fatal "File '$1' has wrong mode: expected $2, but got $mode" + fi +} + +assert_symlink_has_content () { + if ! test -L "$1"; then + fatal "File '$1' is not a symbolic link" + fi + if ! readlink "$1" | grep -q -e "$2"; then + _fatal_print_file "$1" "Symbolic link '$1' doesn't match regexp '$2'" + fi +} + +assert_file_empty() { + if test -s "$1"; then + _fatal_print_file "$1" "File '$1' is not empty" + fi +} + +assert_files_equal() { + if ! cmp "$1" "$2"; then + _fatal_print_files "$1" "$2" "File '$1' and '$2' is not equal" + fi +} + +# Use to skip all of these tests +skip() { + echo "1..0 # SKIP" "$@" + exit 0 +} + +report_err () { + local exit_status="$?" + { { local BASH_XTRACEFD=3; } 2> /dev/null + echo "Unexpected nonzero exit status $exit_status while running: $BASH_COMMAND" >&2 + } 3> /dev/null +} +trap report_err ERR diff --git a/codex-rs/vendor/bubblewrap/tests/libtest.sh b/codex-rs/vendor/bubblewrap/tests/libtest.sh new file mode 100644 index 00000000000..d55bb02bc28 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/libtest.sh @@ -0,0 +1,115 @@ +# shellcheck shell=bash + +# Source library for shell script tests. +# Add non-bubblewrap-specific code to libtest-core.sh instead. +# +# Copyright (C) 2017 Colin Walters +# SPDX-License-Identifier: LGPL-2.0-or-later +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -e + +if [ -n "${G_TEST_SRCDIR:-}" ]; then + test_srcdir="${G_TEST_SRCDIR}/tests" +else + test_srcdir=$(dirname "$0") +fi + +if [ -n "${G_TEST_BUILDDIR:-}" ]; then + test_builddir="${G_TEST_BUILDDIR}/tests" +else + test_builddir=$(dirname "$0") +fi + +. "${test_srcdir}/libtest-core.sh" + +# Make sure /sbin/getpcaps etc. are in our PATH even if non-root +PATH="$PATH:/usr/sbin:/sbin" + +tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX) +touch "${tempdir}/.testtmp" +cleanup() { + if test -n "${TEST_SKIP_CLEANUP:-}"; then + echo "Skipping cleanup of ${tempdir}" + elif test -f "${tempdir}/.testtmp"; then + rm -rf "${tempdir}" + fi +} +trap cleanup EXIT +cd "${tempdir}" + +: "${BWRAP:=bwrap}" +if test -u "$(type -p ${BWRAP})"; then + bwrap_is_suid=true +fi + +FUSE_DIR= +for mp in $(grep " fuse[. ]" /proc/self/mounts | grep "user_id=$(id -u)" | awk '{print $2}'); do + if test -d "$mp"; then + echo "# Using $mp as test fuse mount" + FUSE_DIR="$mp" + break + fi +done + +if test "$(id -u)" = "0"; then + is_uidzero=true +else + is_uidzero=false +fi + +# This is supposed to be an otherwise readable file in an unreadable (by the user) dir +UNREADABLE=/root/.bashrc +if "${is_uidzero}" || test -x "$(dirname "$UNREADABLE")"; then + UNREADABLE= +fi + +# https://github.com/projectatomic/bubblewrap/issues/217 +# are we on a merged-/usr system? +if [ /lib -ef /usr/lib ]; then + BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr + --ro-bind /etc /etc + --dir /var/tmp + --symlink usr/lib /lib + --symlink usr/lib64 /lib64 + --symlink usr/bin /bin + --symlink usr/sbin /sbin + --proc /proc + --dev /dev" +else + BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr + --ro-bind /etc /etc + --ro-bind /bin /bin + --ro-bind-try /lib /lib + --ro-bind-try /lib64 /lib64 + --ro-bind-try /sbin /sbin + --ro-bind-try /nix/store /nix/store + --dir /var/tmp + --proc /proc + --dev /dev" +fi + +# Default arg, bind whole host fs to /, tmpfs on /tmp +RUN="${BWRAP} --bind / / --tmpfs /tmp" + +if [ -z "${BWRAP_MUST_WORK-}" ] && ! $RUN true; then + skip Seems like bwrap is not working at all. Maybe setuid is not working +fi + +extract_child_pid() { + grep child-pid "$1" | sed "s/^.*: \([0-9]*\).*/\1/" +} diff --git a/codex-rs/vendor/bubblewrap/tests/meson.build b/codex-rs/vendor/bubblewrap/tests/meson.build new file mode 100644 index 00000000000..87bf709e990 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/meson.build @@ -0,0 +1,72 @@ +test_programs = [ + ['test-utils', executable( + 'test-utils', + 'test-utils.c', + '../utils.c', + '../utils.h', + dependencies : [selinux_dep], + include_directories : common_include_directories, + )], +] + +executable( + 'try-syscall', + 'try-syscall.c', + override_options: ['b_sanitize=none'], +) + +test_scripts = [ + 'test-run.sh', + 'test-seccomp.py', + 'test-specifying-pidns.sh', + 'test-specifying-userns.sh', +] + +test_env = environment() +test_env.set('BWRAP', bwrap.full_path()) +test_env.set('G_TEST_BUILDDIR', meson.current_build_dir() / '..') +test_env.set('G_TEST_SRCDIR', meson.current_source_dir() / '..') + +foreach pair : test_programs + name = pair[0] + test_program = pair[1] + if meson.version().version_compare('>=0.50.0') + test( + name, + test_program, + env : test_env, + protocol : 'tap', + ) + else + test( + name, + test_program, + env : test_env, + ) + endif +endforeach + +foreach test_script : test_scripts + if test_script.endswith('.py') + interpreter = python + else + interpreter = bash + endif + + if meson.version().version_compare('>=0.50.0') + test( + test_script, + interpreter, + args : [files(test_script)], + env : test_env, + protocol : 'tap', + ) + else + test( + test_script, + interpreter, + args : [files(test_script)], + env : test_env, + ) + endif +endforeach diff --git a/codex-rs/vendor/bubblewrap/tests/test-run.sh b/codex-rs/vendor/bubblewrap/tests/test-run.sh new file mode 100755 index 00000000000..1003e8c4b1d --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/test-run.sh @@ -0,0 +1,692 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +srcd=$(cd $(dirname "$0") && pwd) + +. ${srcd}/libtest.sh + +bn=$(basename "$0") + +test_count=0 +ok () { + test_count=$((test_count + 1)) + echo ok $test_count "$@" +} +ok_skip () { + ok "# SKIP" "$@" +} +done_testing () { + echo "1..$test_count" +} + +# Test help +${BWRAP} --help > help.txt +assert_file_has_content help.txt "usage: ${BWRAP}" +ok "Help works" + +for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare-pid"; do + # Test fuse fs as bind source + if [ "x$FUSE_DIR" != "x" ]; then + $RUN $ALT --proc /proc --dev /dev --bind $FUSE_DIR /tmp/foo true + ok "can bind-mount a FUSE directory with $ALT" + else + ok_skip "no FUSE support" + fi + # no --dev => no devpts => no map_root workaround + $RUN $ALT --proc /proc true + ok "can mount /proc with $ALT" + # No network + $RUN $ALT --unshare-net --proc /proc --dev /dev true + ok "can unshare network, create new /dev with $ALT" + # Unreadable file + echo -n "expect EPERM: " >&2 + + # Test caps when bwrap is not setuid + if test -n "${bwrap_is_suid:-}"; then + CAP="--cap-add ALL" + else + CAP="" + fi + + if ! cat /etc/shadow >/dev/null && + $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /tmp/foo; then + assert_not_reached Could read /etc/shadow via /tmp/foo bind-mount + fi + + if ! cat /etc/shadow >/dev/null && + $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /etc/shadow; then + assert_not_reached Could read /etc/shadow + fi + + ok "cannot read /etc/shadow with $ALT" + # Unreadable dir + if [ "x$UNREADABLE" != "x" ]; then + echo -n "expect EPERM: " >&2 + if $RUN $ALT --unshare-net --proc /proc --dev /dev --bind $UNREADABLE /tmp/foo cat /tmp/foo; then + assert_not_reached Could read $UNREADABLE + fi + ok "cannot read $UNREADABLE with $ALT" + else + ok_skip "not sure what unreadable file to use" + fi + + # bind dest in symlink (https://github.com/projectatomic/bubblewrap/pull/119) + $RUN $ALT --dir /tmp/dir --symlink dir /tmp/link --bind /etc /tmp/link true + ok "can bind a destination over a symlink" +done + +# Test symlink behaviour +rm -f ./symlink +$RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2 +readlink ./symlink > target.txt +assert_file_has_content target.txt /dev/null +ok "--symlink works" +$RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2 +ok "--symlink is idempotent" +if $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/full "$(pwd)/symlink" true 2>err.txt; then + fatal "creating a conflicting symlink should have failed" +else + assert_file_has_content err.txt "Can't make symlink .*: existing destination is /dev/null" +fi +ok "--symlink doesn't overwrite a conflicting symlink" + +# Test devices +$RUN --unshare-pid --dev /dev ls -al /dev/{stdin,stdout,stderr,null,random,urandom,fd,core} >/dev/null +ok "all expected devices were created" + +# Test --as-pid-1 +$RUN --unshare-pid --as-pid-1 --bind / / bash -c 'echo $$' > as_pid_1.txt +assert_file_has_content as_pid_1.txt "1" +ok "can run as pid 1" + +# Test --info-fd and --json-status-fd +if $RUN --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'exit 42' 42>info.json 43>json-status.json 2>err.txt; then + fatal "should have been exit 42" +fi +assert_file_has_content info.json '"child-pid": [0-9]' +assert_file_has_content json-status.json '"child-pid": [0-9]' +assert_file_has_content_literal json-status.json '"exit-code": 42' +ok "info and json-status fd" + +DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L -c "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt) + +for NS in "ipc" "mnt" "net" "pid" "uts"; do + + want=$(echo "$DATA" | grep "/proc/self/ns/$NS" | awk '{print $2}') + assert_file_has_content info.json "$want" + assert_file_has_content json-status.json "$want" +done + +ok "namespace id info in info and json-status fd" + +if ! command -v strace >/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then + ok_skip "no strace fault injection" +else + ! strace -o /dev/null -f -e trace=prctl -e fault=prctl:when=39 $RUN --die-with-parent --json-status-fd 42 true 42>json-status.json + assert_not_file_has_content json-status.json '"exit-code": [0-9]' + ok "pre-exec failure doesn't include exit-code in json-status" +fi + +notanexecutable=/ +$RUN --json-status-fd 42 $notanexecutable 42>json-status.json || true +assert_not_file_has_content json-status.json '"exit-code": [0-9]' +ok "exec failure doesn't include exit-code in json-status" + +# These tests require --unshare-user +if test -n "${bwrap_is_suid:-}"; then + ok_skip "no --cap-add support" + ok_skip "no --cap-add support" + ok_skip "no --disable-userns" +else + BWRAP_RECURSE="$BWRAP --unshare-user --uid 0 --gid 0 --cap-add ALL --bind / / --bind /proc /proc" + + # $BWRAP May be inaccessible due to the user namespace so use /proc/self/exe + $BWRAP_RECURSE -- /proc/self/exe --unshare-all --bind / / --bind /proc /proc echo hello > recursive_proc.txt + assert_file_has_content recursive_proc.txt "hello" + ok "can mount /proc recursively" + + $BWRAP_RECURSE -- /proc/self/exe --unshare-all ${BWRAP_RO_HOST_ARGS} findmnt > recursive-newroot.txt + assert_file_has_content recursive-newroot.txt "/usr" + ok "can pivot to new rootfs recursively" + + $BWRAP --dev-bind / / -- true + ! $BWRAP --assert-userns-disabled --dev-bind / / -- true + $BWRAP --unshare-user --disable-userns --dev-bind / / -- true + ! $BWRAP --unshare-user --disable-userns --dev-bind / / -- $BWRAP --dev-bind / / -- true + $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" + $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" + $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true" + + $BWRAP_RECURSE --dev-bind / / -- true + ! $BWRAP_RECURSE --assert-userns-disabled --dev-bind / / -- true + $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- true + ! $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- /proc/self/exe --dev-bind / / -- true + $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" + $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" + $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true" + + ok "can disable nested userns" +fi + +# Test error prefixing +if $RUN --unshare-pid --bind /source-enoent /dest true 2>err.txt; then + assert_not_reached "bound nonexistent source" +fi +assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent" +ok "error prefixing" + +if ! ${is_uidzero}; then + # When invoked as non-root, check that by default we have no caps left + for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" "--as-pid-1"; do + e=0 + $RUN $OPT --unshare-pid getpcaps 1 >&2 2> caps.test || e=$? + sed -e 's/^/# /' < caps.test >&2 + test "$e" = 0 + assert_not_file_has_content caps.test ': =.*cap' + done + ok "we have no caps as uid != 0" +else + capsh --print | sed -e 's/no-new-privs=0/no-new-privs=1/' > caps.expected + + for OPT in "" "--as-pid-1"; do + $RUN $OPT --unshare-pid capsh --print >caps.test + diff -u caps.expected caps.test + done + # And test that we can drop all, as well as specific caps + $RUN $OPT --cap-drop ALL --unshare-pid capsh --print >caps.test + assert_file_has_content caps.test 'Current: =$' + # Check for dropping kill/fowner (we assume all uid 0 callers have this) + # But we should still have net_bind_service for example + $RUN $OPT --cap-drop CAP_KILL --cap-drop CAP_FOWNER --unshare-pid capsh --print >caps.test + # capsh's output format changed from v2.29 -> drops are now indicated with -eip + if grep 'Current: =.*+eip$' caps.test; then + assert_not_file_has_content caps.test '^Current: =.*cap_kill.*+eip$' + assert_not_file_has_content caps.test '^Current: =.*cap_fowner.*+eip$' + assert_file_has_content caps.test '^Current: =.*cap_net_bind_service.*+eip$' + else + assert_file_has_content caps.test '^Current: =eip.*cap_kill.*-eip$' + assert_file_has_content caps.test '^Current: =eip.*cap_fowner.*-eip$' + assert_not_file_has_content caps.test '^Current: =.*cap_net_bind_service.*-eip$' + fi + ok "we have the expected caps as uid 0" +fi + +# Test --die-with-parent + +cat >lockf-n.py < test.args +printf '%s--dir\0/tmp/hello/world2\0' '' > test.args2 +printf '%s--dir\0/tmp/hello/world3\0' '' > test.args3 +$RUN --args 3 --args 4 --args 5 /bin/sh -c 'test -d /tmp/hello/world && test -d /tmp/hello/world2 && test -d /tmp/hello/world3' 3 bin/--inadvisable-executable-name-- +echo "echo hello" >> bin/--inadvisable-executable-name-- +chmod +x bin/--inadvisable-executable-name-- +PATH="${srcd}:$PATH" $RUN -- sh -c "echo hello" > stdout +assert_file_has_content stdout hello +ok "we can run with --" +PATH="$(pwd)/bin:$PATH" $RUN -- --inadvisable-executable-name-- > stdout +assert_file_has_content stdout hello +ok "we can run an inadvisable executable name with --" +if $RUN -- --dev-bind /dev /dev sh -c 'echo should not have run'; then + assert_not_reached "'--dev-bind' should have been interpreted as a (silly) executable name" +fi +ok "options like --dev-bind are defanged by --" + +if command -v mktemp > /dev/null; then + tempfile="$(mktemp /tmp/bwrap-test-XXXXXXXX)" + echo "hello" > "$tempfile" + $BWRAP --bind / / cat "$tempfile" > stdout + assert_file_has_content stdout hello + ok "bind-mount of / exposes real /tmp" + $BWRAP --bind / / --bind /tmp /tmp cat "$tempfile" > stdout + assert_file_has_content stdout hello + ok "bind-mount of /tmp exposes real /tmp" + if [ -d /mnt ] && [ ! -L /mnt ]; then + $BWRAP --bind / / --bind /tmp /mnt cat "/mnt/${tempfile#/tmp/}" > stdout + assert_file_has_content stdout hello + ok "bind-mount of /tmp onto /mnt exposes real /tmp" + else + ok_skip "/mnt does not exist or is a symlink" + fi +else + ok_skip "mktemp not found" + ok_skip "mktemp not found" + ok_skip "mktemp not found" +fi + +if $RUN test -d /tmp/oldroot; then + assert_not_reached "/tmp/oldroot should not be visible" +fi +if $RUN test -d /tmp/newroot; then + assert_not_reached "/tmp/newroot should not be visible" +fi + +echo "hello" > input.$$ +$BWRAP --bind / / --bind "$(pwd)" /tmp cat /tmp/input.$$ > stdout +assert_file_has_content stdout hello +if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/oldroot; then + assert_not_reached "/tmp/oldroot should not be visible" +fi +if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/newroot; then + assert_not_reached "/tmp/newroot should not be visible" +fi +ok "we can mount another directory onto /tmp" + +echo "hello" > input.$$ +$RUN --bind "$(pwd)" /tmp/here cat /tmp/here/input.$$ > stdout +assert_file_has_content stdout hello +if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/oldroot; then + assert_not_reached "/tmp/oldroot should not be visible" +fi +if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/newroot; then + assert_not_reached "/tmp/newroot should not be visible" +fi +ok "we can mount another directory inside /tmp" + +touch some-file +mkdir -p some-dir +rm -fr new-dir-mountpoint +rm -fr new-file-mountpoint +$RUN \ + --bind "$(pwd -P)/some-dir" "$(pwd -P)/new-dir-mountpoint" \ + --bind "$(pwd -P)/some-file" "$(pwd -P)/new-file-mountpoint" \ + true +command stat -c '%a' new-dir-mountpoint > new-dir-permissions +assert_file_has_content new-dir-permissions 755 +command stat -c '%a' new-file-mountpoint > new-file-permissions +assert_file_has_content new-file-permissions 444 +ok "Files and directories created as mount points have expected permissions" + + +if [ -S /dev/log ]; then + $RUN --bind / / --bind "$(realpath /dev/log)" "$(realpath /dev/log)" true + ok "Can bind-mount a socket (/dev/log) onto a socket" +else + ok_skip "- /dev/log is not a socket, cannot test bubblewrap#409" +fi + +mkdir -p dir-already-existed +chmod 0710 dir-already-existed +mkdir -p dir-already-existed2 +chmod 0754 dir-already-existed2 +rm -fr new-dir-default-perms +rm -fr new-dir-set-perms +$RUN \ + --perms 1741 --dir "$(pwd -P)/new-dir-set-perms" \ + --dir "$(pwd -P)/dir-already-existed" \ + --perms 0741 --dir "$(pwd -P)/dir-already-existed2" \ + --dir "$(pwd -P)/dir-chmod" \ + --chmod 1755 "$(pwd -P)/dir-chmod" \ + --dir "$(pwd -P)/new-dir-default-perms" \ + true +command stat -c '%a' new-dir-default-perms > new-dir-permissions +assert_file_has_content new-dir-permissions '^755$' +command stat -c '%a' new-dir-set-perms > new-dir-permissions +assert_file_has_content new-dir-permissions '^1741$' +command stat -c '%a' dir-already-existed > dir-permissions +assert_file_has_content dir-permissions '^710$' +command stat -c '%a' dir-already-existed2 > dir-permissions +assert_file_has_content dir-permissions '^754$' +command stat -c '%a' dir-chmod > dir-permissions +assert_file_has_content dir-permissions '^1755$' +ok "Directories created explicitly have expected permissions" + +rm -fr parent +rm -fr parent-of-1777 +rm -fr parent-of-0755 +rm -fr parent-of-0644 +rm -fr parent-of-0750 +rm -fr parent-of-0710 +rm -fr parent-of-0720 +rm -fr parent-of-0640 +rm -fr parent-of-0700 +rm -fr parent-of-0600 +rm -fr parent-of-0705 +rm -fr parent-of-0604 +rm -fr parent-of-0000 +$RUN \ + --dir "$(pwd -P)"/parent/dir \ + --perms 1777 --dir "$(pwd -P)"/parent-of-1777/dir \ + --perms 0755 --dir "$(pwd -P)"/parent-of-0755/dir \ + --perms 0644 --dir "$(pwd -P)"/parent-of-0644/dir \ + --perms 0750 --dir "$(pwd -P)"/parent-of-0750/dir \ + --perms 0710 --dir "$(pwd -P)"/parent-of-0710/dir \ + --perms 0720 --dir "$(pwd -P)"/parent-of-0720/dir \ + --perms 0640 --dir "$(pwd -P)"/parent-of-0640/dir \ + --perms 0700 --dir "$(pwd -P)"/parent-of-0700/dir \ + --perms 0600 --dir "$(pwd -P)"/parent-of-0600/dir \ + --perms 0705 --dir "$(pwd -P)"/parent-of-0705/dir \ + --perms 0604 --dir "$(pwd -P)"/parent-of-0604/dir \ + --perms 0000 --dir "$(pwd -P)"/parent-of-0000/dir \ + true +command stat -c '%a' parent > dir-permissions +assert_file_has_content dir-permissions '^755$' +command stat -c '%a' parent-of-1777 > dir-permissions +assert_file_has_content dir-permissions '^755$' +command stat -c '%a' parent-of-0755 > dir-permissions +assert_file_has_content dir-permissions '^755$' +command stat -c '%a' parent-of-0644 > dir-permissions +assert_file_has_content dir-permissions '^755$' +command stat -c '%a' parent-of-0750 > dir-permissions +assert_file_has_content dir-permissions '^750$' +command stat -c '%a' parent-of-0710 > dir-permissions +assert_file_has_content dir-permissions '^750$' +command stat -c '%a' parent-of-0720 > dir-permissions +assert_file_has_content dir-permissions '^750$' +command stat -c '%a' parent-of-0640 > dir-permissions +assert_file_has_content dir-permissions '^750$' +command stat -c '%a' parent-of-0700 > dir-permissions +assert_file_has_content dir-permissions '^700$' +command stat -c '%a' parent-of-0600 > dir-permissions +assert_file_has_content dir-permissions '^700$' +command stat -c '%a' parent-of-0705 > dir-permissions +assert_file_has_content dir-permissions '^705$' +command stat -c '%a' parent-of-0604 > dir-permissions +assert_file_has_content dir-permissions '^705$' +command stat -c '%a' parent-of-0000 > dir-permissions +assert_file_has_content dir-permissions '^700$' +chmod -R 0700 parent* +rm -fr parent* +ok "Directories created as parents have expected permissions" + +$RUN \ + --perms 01777 --tmpfs "$(pwd -P)" \ + cat /proc/self/mountinfo >&2 +$RUN \ + --perms 01777 --tmpfs "$(pwd -P)" \ + stat -c '%a' "$(pwd -P)" > dir-permissions +assert_file_has_content dir-permissions '^1777$' +$RUN \ + --tmpfs "$(pwd -P)" \ + stat -c '%a' "$(pwd -P)" > dir-permissions +assert_file_has_content dir-permissions '^755$' +ok "tmpfs has expected permissions" + +# 1048576 = 1 MiB +if test -n "${bwrap_is_suid:-}"; then + if $RUN --size 1048576 --tmpfs "$(pwd -P)" true; then + assert_not_reached "Should not allow --size --tmpfs when setuid" + fi + ok "--size --tmpfs is not allowed when setuid" +elif df --output=size --block-size=1K "$(pwd -P)" >/dev/null 2>/dev/null; then + $RUN \ + --size 1048576 --tmpfs "$(pwd -P)" \ + df --output=size --block-size=1K "$(pwd -P)" > dir-size + assert_file_has_content dir-size '^ *1024$' + $RUN \ + --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \ + stat -c '%a' "$(pwd -P)" > dir-permissions + assert_file_has_content dir-permissions '^1777$' + $RUN \ + --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \ + df --output=size --block-size=1K "$(pwd -P)" > dir-size + assert_file_has_content dir-size '^ *1024$' + $RUN \ + --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \ + stat -c '%a' "$(pwd -P)" > dir-permissions + assert_file_has_content dir-permissions '^1777$' + $RUN \ + --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \ + df --output=size --block-size=1K "$(pwd -P)" > dir-size + assert_file_has_content dir-size '^ *1024$' + ok "tmpfs has expected size" +else + $RUN --size 1048576 --tmpfs "$(pwd -P)" true + $RUN --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" true + $RUN --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" true + ok_skip "df is too old, cannot test --size --tmpfs fully" +fi + +$RUN \ + --file 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^666$' +$RUN \ + --perms 0640 --file 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^640$' +$RUN \ + --bind-data 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^600$' +$RUN \ + --perms 0640 --bind-data 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^640$' +$RUN \ + --ro-bind-data 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^600$' +$RUN \ + --perms 0640 --ro-bind-data 0 /tmp/file \ + stat -c '%a' /tmp/file < /dev/null > file-permissions +assert_file_has_content file-permissions '^640$' +ok "files have expected permissions" + +if $RUN --size 0 --tmpfs /tmp/a true; then + assert_not_reached Zero tmpfs size allowed +fi +if $RUN --size 123bogus --tmpfs /tmp/a true; then + assert_not_reached Bogus tmpfs size allowed +fi +if $RUN --size '' --tmpfs /tmp/a true; then + assert_not_reached Empty tmpfs size allowed +fi +if $RUN --size -12345678 --tmpfs /tmp/a true; then + assert_not_reached Negative tmpfs size allowed +fi +if $RUN --size ' -12345678' --tmpfs /tmp/a true; then + assert_not_reached Negative tmpfs size with space allowed +fi +# This is 2^64 +if $RUN --size 18446744073709551616 --tmpfs /tmp/a true; then + assert_not_reached Overflowing tmpfs size allowed +fi +# This is 2^63 + 1; note that the current max size is SIZE_MAX/2 +if $RUN --size 9223372036854775809 --tmpfs /tmp/a true; then + assert_not_reached Too-large tmpfs size allowed +fi +ok "bogus tmpfs size not allowed" + +if $RUN --perms 0640 --perms 0640 --tmpfs /tmp/a true; then + assert_not_reached Multiple perms options allowed +fi +if $RUN --size 1048576 --size 1048576 --tmpfs /tmp/a true; then + assert_not_reached Multiple perms options allowed +fi +ok "--perms and --size only allowed once" + + +FOO= BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout +assert_file_has_content stdout barbaz +FOO=wrong BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout +assert_file_has_content stdout barbaz +FOO=wrong BAR=baz $RUN --unsetenv FOO sh -c 'printf "%s%s" "$FOO" "$BAR"' > stdout +printf baz > reference +assert_files_equal stdout reference +FOO=wrong BAR=wrong $RUN --clearenv /usr/bin/env > stdout +echo "PWD=$(pwd -P)" > reference +assert_files_equal stdout reference +ok "environment manipulation" + +$RUN sh -c 'echo $0' > stdout +assert_file_has_content stdout sh +$RUN --argv0 sh sh -c 'echo $0' > stdout +assert_file_has_content stdout sh +$RUN --argv0 right sh -c 'echo $0' > stdout +assert_file_has_content stdout right +ok "argv0 manipulation" + +echo "foobar" > file-data +$RUN --proc /proc --dev /dev --bind / / --bind-fd 100 /tmp cat /tmp/file-data 100< . > stdout +assert_file_has_content stdout foobar + +ok "bind-fd" + +$RUN --chdir / --chdir / true > stdout 2>&1 +assert_file_has_content stdout '^bwrap: Only the last --chdir option will take effect$' +ok "warning logged for redundant --chdir" + +$RUN --level-prefix --chdir / --chdir / true > stdout 2>&1 +assert_file_has_content stdout '^<4>bwrap: Only the last --chdir option will take effect$' +ok "--level-prefix" + +if test -n "${bwrap_is_suid:-}"; then + ok_skip "no --overlay support" + ok_skip "no --overlay support" + ok_skip "no --tmp-overlay support" + ok_skip "no --ro-overlay support" + ok_skip "no --overlay-src support" +else + mkdir lower1 lower2 upper work + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + printf 4 > upper/a + + # Check if unprivileged overlayfs is available + if ! unshare -rm mount -t overlay -o lowerdir=lower1,upperdir=upper,workdir=work,userxattr overlay lower2; then + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + ok_skip "no kernel support for unprivileged overlayfs" + else + + # Test --overlay + if $RUN --overlay upper work /tmp true 2>err.txt; then + assert_not_reached At least one --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --overlay requires at least one --overlay-src" + $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^2$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z sh -c 'printf 5 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^5$' + assert_file_has_content upper/b '^5$' + ok "--overlay" + + # Test --overlay path escaping + # Coincidentally, ":,\ is the face I make contemplating anyone who might + # need this functionality, not that that's going to stop me from supporting + # it. + mkdir 'lower ":,\' 'upper ":,\' 'work ":,\' + printf 1 > 'lower ":,\'/a + $RUN --overlay-src 'lower ":,\' --overlay 'upper ":,\' 'work ":,\' /tmp/x sh -c 'cat /tmp/x/a; printf 2 > /tmp/x/a; cat /tmp/x/a' > stdout + assert_file_has_content stdout '^12$' + assert_file_has_content 'lower ":,\'/a '^1$' + assert_file_has_content 'upper ":,\'/a '^2$' + ok "--overlay path escaping" + + # Test --tmp-overlay + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + if $RUN --tmp-overlay /tmp true 2>err.txt; then + assert_not_reached At least one --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --tmp-overlay requires at least one --overlay-src" + $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^2$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^4$' + $RUN --overlay-src lower1 --tmp-overlay /tmp/x --overlay-src lower2 --tmp-overlay /tmp/y sh -c 'cat /tmp/x/b; printf 4 > /tmp/x/b; cat /tmp/x/b; cat /tmp/y/b' > stdout + assert_file_has_content stdout '^243$' + assert_file_has_content lower1/b '^2$' + assert_file_has_content lower2/b '^3$' + ok "--tmp-overlay" + + # Test --ro-overlay + printf 1 > lower1/a + printf 2 > lower1/b + printf 3 > lower2/b + if $RUN --ro-overlay /tmp true 2>err.txt; then + assert_not_reached At least two --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src" + if $RUN --overlay-src lower1 --ro-overlay /tmp true 2>err.txt; then + assert_not_reached At least two --overlay-src not required + fi + assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src" + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout + assert_file_has_content stdout '^1$' + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout + assert_file_has_content stdout '^3$' + $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout + assert_file_has_content stdout '^3$' + ok "--ro-overlay" + + # Test --overlay-src restrictions + if $RUN --overlay-src /tmp true 2>err.txt; then + assert_not_reached Trailing --overlay-src allowed + fi + assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay" + if $RUN --overlay-src /tmp --chdir / true 2>err.txt; then + assert_not_reached --overlay-src allowed to precede non-overlay options + fi + assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay" + ok "--overlay-src restrictions" + + fi +fi + +done_testing diff --git a/codex-rs/vendor/bubblewrap/tests/test-seccomp.py b/codex-rs/vendor/bubblewrap/tests/test-seccomp.py new file mode 100755 index 00000000000..3ce02ef888f --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/test-seccomp.py @@ -0,0 +1,635 @@ +#!/usr/bin/env python3 +# Copyright 2021 Simon McVittie +# SPDX-License-Identifier: LGPL-2.0-or-later + +import errno +import logging +import os +import subprocess +import sys +import tempfile +import termios +import unittest + +try: + import seccomp +except ImportError: + print('1..0 # SKIP cannot import seccomp Python module') + sys.exit(0) + + +# This is the @default set from systemd as of 2021-10-11 +DEFAULT_SET = set(''' +brk +cacheflush +clock_getres +clock_getres_time64 +clock_gettime +clock_gettime64 +clock_nanosleep +clock_nanosleep_time64 +execve +exit +exit_group +futex +futex_time64 +get_robust_list +get_thread_area +getegid +getegid32 +geteuid +geteuid32 +getgid +getgid32 +getgroups +getgroups32 +getpgid +getpgrp +getpid +getppid +getrandom +getresgid +getresgid32 +getresuid +getresuid32 +getrlimit +getsid +gettid +gettimeofday +getuid +getuid32 +membarrier +mmap +mmap2 +munmap +nanosleep +pause +prlimit64 +restart_syscall +rseq +rt_sigreturn +sched_getaffinity +sched_yield +set_robust_list +set_thread_area +set_tid_address +set_tls +sigreturn +time +ugetrlimit +'''.split()) + +# This is the @basic-io set from systemd +BASIC_IO_SET = set(''' +_llseek +close +close_range +dup +dup2 +dup3 +lseek +pread64 +preadv +preadv2 +pwrite64 +pwritev +pwritev2 +read +readv +write +writev +'''.split()) + +# This is the @filesystem-io set from systemd +FILESYSTEM_SET = set(''' +access +chdir +chmod +close +creat +faccessat +faccessat2 +fallocate +fchdir +fchmod +fchmodat +fcntl +fcntl64 +fgetxattr +flistxattr +fremovexattr +fsetxattr +fstat +fstat64 +fstatat64 +fstatfs +fstatfs64 +ftruncate +ftruncate64 +futimesat +getcwd +getdents +getdents64 +getxattr +inotify_add_watch +inotify_init +inotify_init1 +inotify_rm_watch +lgetxattr +link +linkat +listxattr +llistxattr +lremovexattr +lsetxattr +lstat +lstat64 +mkdir +mkdirat +mknod +mknodat +newfstatat +oldfstat +oldlstat +oldstat +open +openat +openat2 +readlink +readlinkat +removexattr +rename +renameat +renameat2 +rmdir +setxattr +stat +stat64 +statfs +statfs64 +statx +symlink +symlinkat +truncate +truncate64 +unlink +unlinkat +utime +utimensat +utimensat_time64 +utimes +'''.split()) + +# Miscellaneous syscalls used during process startup, at least on x86_64 +ALLOWED = DEFAULT_SET | BASIC_IO_SET | FILESYSTEM_SET | set(''' +arch_prctl +ioctl +madvise +mprotect +mremap +prctl +readdir +umask +'''.split()) + +# Syscalls we will try to use, expecting them to be either allowed or +# blocked by our allow and/or deny lists +TRY_SYSCALLS = [ + 'chmod', + 'chroot', + 'clone3', + 'ioctl TIOCNOTTY', + 'ioctl TIOCSTI CVE-2019-10063', + 'ioctl TIOCSTI', + 'listen', + 'prctl', +] + + +class Test(unittest.TestCase): + def setUp(self) -> None: + here = os.path.dirname(os.path.abspath(__file__)) + + if 'G_TEST_SRCDIR' in os.environ: + self.test_srcdir = os.getenv('G_TEST_SRCDIR') + '/tests' + else: + self.test_srcdir = here + + if 'G_TEST_BUILDDIR' in os.environ: + self.test_builddir = os.getenv('G_TEST_BUILDDIR') + '/tests' + else: + self.test_builddir = here + + self.bwrap = os.getenv('BWRAP', 'bwrap') + self.try_syscall = os.path.join(self.test_builddir, 'try-syscall') + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + 'true', + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=2, + ) + + if completed.returncode != 0: + raise unittest.SkipTest( + 'cannot run bwrap (does it need to be setuid?)' + ) + + def tearDown(self) -> None: + pass + + def test_no_seccomp(self) -> None: + for syscall in TRY_SYSCALLS: + print('# {} without seccomp'.format(syscall)) + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + self.try_syscall, syscall, + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=2, + ) + + if ( + syscall == 'ioctl TIOCSTI CVE-2019-10063' + and completed.returncode == errno.ENOENT + ): + print('# Cannot test 64-bit syscall parameter on 32-bit') + continue + + if syscall == 'clone3': + # If the kernel supports it, we didn't block it so + # it fails with EFAULT. If the kernel doesn't support it, + # it'll fail with ENOSYS instead. + self.assertIn( + completed.returncode, + (errno.ENOSYS, errno.EFAULT), + ) + elif syscall.startswith('ioctl') or syscall == 'listen': + self.assertEqual(completed.returncode, errno.EBADF) + else: + self.assertEqual(completed.returncode, errno.EFAULT) + + def test_seccomp_allowlist(self) -> None: + with tempfile.TemporaryFile() as allowlist_temp: + allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS)) + + if os.uname().machine == 'x86_64': + # Allow Python and try-syscall to be different word sizes + allowlist.add_arch(seccomp.Arch.X86) + + for syscall in ALLOWED: + try: + allowlist.add_rule(seccomp.ALLOW, syscall) + except Exception as e: + print('# Cannot add {} to allowlist: {!r}'.format(syscall, e)) + + allowlist.export_bpf(allowlist_temp) + + for syscall in TRY_SYSCALLS: + print('# allowlist vs. {}'.format(syscall)) + allowlist_temp.seek(0, os.SEEK_SET) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--seccomp', str(allowlist_temp.fileno()), + self.try_syscall, syscall, + ], + pass_fds=(allowlist_temp.fileno(),), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=2, + ) + + if ( + syscall == 'ioctl TIOCSTI CVE-2019-10063' + and completed.returncode == errno.ENOENT + ): + print('# Cannot test 64-bit syscall parameter on 32-bit') + continue + + if syscall.startswith('ioctl'): + # We allow this, so it is executed (and in this simple + # example, immediately fails) + self.assertEqual(completed.returncode, errno.EBADF) + elif syscall in ('chroot', 'listen', 'clone3'): + # We don't allow these, so they fail with ENOSYS. + # clone3 might also be failing with ENOSYS because + # the kernel genuinely doesn't support it. + self.assertEqual(completed.returncode, errno.ENOSYS) + else: + # We allow this, so it is executed (and in this simple + # example, immediately fails) + self.assertEqual(completed.returncode, errno.EFAULT) + + def test_seccomp_denylist(self) -> None: + with tempfile.TemporaryFile() as denylist_temp: + denylist = seccomp.SyscallFilter(seccomp.ALLOW) + + if os.uname().machine == 'x86_64': + # Allow Python and try-syscall to be different word sizes + denylist.add_arch(seccomp.Arch.X86) + + # Using ECONNREFUSED here because it's unlikely that any of + # these syscalls will legitimately fail with that code, so + # if they fail like this, it will be as a result of seccomp. + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod') + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot') + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl') + denylist.add_rule( + seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl', + seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI), + ) + + denylist.export_bpf(denylist_temp) + + for syscall in TRY_SYSCALLS: + print('# denylist vs. {}'.format(syscall)) + denylist_temp.seek(0, os.SEEK_SET) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--seccomp', str(denylist_temp.fileno()), + self.try_syscall, syscall, + ], + pass_fds=(denylist_temp.fileno(),), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=2, + ) + + if ( + syscall == 'ioctl TIOCSTI CVE-2019-10063' + and completed.returncode == errno.ENOENT + ): + print('# Cannot test 64-bit syscall parameter on 32-bit') + continue + + if syscall == 'clone3': + # If the kernel supports it, we didn't block it so + # it fails with EFAULT. If the kernel doesn't support it, + # it'll fail with ENOSYS instead. + self.assertIn( + completed.returncode, + (errno.ENOSYS, errno.EFAULT), + ) + elif syscall in ('ioctl TIOCNOTTY', 'listen'): + # Not on the denylist + self.assertEqual(completed.returncode, errno.EBADF) + else: + # We blocked all of these + self.assertEqual(completed.returncode, errno.ECONNREFUSED) + + def test_seccomp_stacked(self, allowlist_first=False) -> None: + with tempfile.TemporaryFile( + ) as allowlist_temp, tempfile.TemporaryFile( + ) as denylist_temp: + # This filter is a simplified version of what Flatpak wants + + allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS)) + denylist = seccomp.SyscallFilter(seccomp.ALLOW) + + if os.uname().machine == 'x86_64': + # Allow Python and try-syscall to be different word sizes + allowlist.add_arch(seccomp.Arch.X86) + denylist.add_arch(seccomp.Arch.X86) + + for syscall in ALLOWED: + try: + allowlist.add_rule(seccomp.ALLOW, syscall) + except Exception as e: + print('# Cannot add {} to allowlist: {!r}'.format(syscall, e)) + + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod') + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot') + denylist.add_rule( + seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl', + seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI), + ) + + # All seccomp programs except the last must allow prctl(), + # because otherwise we wouldn't be able to add the remaining + # seccomp programs. We document that the last program can + # block prctl, so test that. + if allowlist_first: + denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl') + + allowlist.export_bpf(allowlist_temp) + denylist.export_bpf(denylist_temp) + + for syscall in TRY_SYSCALLS: + print('# stacked vs. {}'.format(syscall)) + allowlist_temp.seek(0, os.SEEK_SET) + denylist_temp.seek(0, os.SEEK_SET) + + if allowlist_first: + fds = [allowlist_temp.fileno(), denylist_temp.fileno()] + else: + fds = [denylist_temp.fileno(), allowlist_temp.fileno()] + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--add-seccomp-fd', str(fds[0]), + '--add-seccomp-fd', str(fds[1]), + self.try_syscall, syscall, + ], + pass_fds=fds, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=2, + ) + + if ( + syscall == 'ioctl TIOCSTI CVE-2019-10063' + and completed.returncode == errno.ENOENT + ): + print('# Cannot test 64-bit syscall parameter on 32-bit') + continue + + if syscall == 'ioctl TIOCNOTTY': + # Not denied by the denylist, and allowed by the allowlist + self.assertEqual(completed.returncode, errno.EBADF) + elif syscall in ('clone3', 'listen'): + # We didn't deny these, so the denylist has no effect + # and we fall back to the allowlist, which doesn't + # include them either. + # clone3 might also be failing with ENOSYS because + # the kernel genuinely doesn't support it. + self.assertEqual(completed.returncode, errno.ENOSYS) + elif syscall == 'chroot': + # This is denied by the denylist *and* not allowed by + # the allowlist. The result depends which one we added + # first: the most-recently-added filter "wins". + if allowlist_first: + self.assertEqual( + completed.returncode, + errno.ECONNREFUSED, + ) + else: + self.assertEqual(completed.returncode, errno.ENOSYS) + elif syscall == 'prctl': + # We can only put this on the denylist if the denylist + # is the last to be added. + if allowlist_first: + self.assertEqual( + completed.returncode, + errno.ECONNREFUSED, + ) + else: + self.assertEqual(completed.returncode, errno.EFAULT) + else: + # chmod is allowed by the allowlist but blocked by the + # denylist. Denying takes precedence over allowing, + # regardless of order. + self.assertEqual(completed.returncode, errno.ECONNREFUSED) + + def test_seccomp_stacked_allowlist_first(self) -> None: + self.test_seccomp_stacked(allowlist_first=True) + + def test_seccomp_invalid(self) -> None: + with tempfile.TemporaryFile( + ) as allowlist_temp, tempfile.TemporaryFile( + ) as denylist_temp: + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--add-seccomp-fd', '-1', + 'true', + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn(b'bwrap: Invalid fd: -1\n', completed.stderr) + self.assertEqual(completed.returncode, 1) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--seccomp', '0a', + 'true', + ], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn(b'bwrap: Invalid fd: 0a\n', completed.stderr) + self.assertEqual(completed.returncode, 1) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--add-seccomp-fd', str(denylist_temp.fileno()), + '--seccomp', str(allowlist_temp.fileno()), + 'true', + ], + pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn( + b'bwrap: --seccomp cannot be combined with --add-seccomp-fd\n', + completed.stderr, + ) + self.assertEqual(completed.returncode, 1) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--seccomp', str(allowlist_temp.fileno()), + '--add-seccomp-fd', str(denylist_temp.fileno()), + 'true', + ], + pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn( + b'--add-seccomp-fd cannot be combined with --seccomp', + completed.stderr, + ) + self.assertEqual(completed.returncode, 1) + + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--add-seccomp-fd', str(allowlist_temp.fileno()), + '--add-seccomp-fd', str(allowlist_temp.fileno()), + 'true', + ], + pass_fds=(allowlist_temp.fileno(), allowlist_temp.fileno()), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn( + b"bwrap: Can't read seccomp data: ", + completed.stderr, + ) + self.assertEqual(completed.returncode, 1) + + allowlist_temp.write(b'\x01') + allowlist_temp.seek(0, os.SEEK_SET) + completed = subprocess.run( + [ + self.bwrap, + '--ro-bind', '/', '/', + '--add-seccomp-fd', str(denylist_temp.fileno()), + '--add-seccomp-fd', str(allowlist_temp.fileno()), + 'true', + ], + pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + self.assertIn( + b'bwrap: Invalid seccomp data, must be multiple of 8\n', + completed.stderr, + ) + self.assertEqual(completed.returncode, 1) + + +def main(): + logging.basicConfig(level=logging.DEBUG) + + try: + from tap.runner import TAPTestRunner + except ImportError: + TAPTestRunner = None # type: ignore + + if TAPTestRunner is not None: + runner = TAPTestRunner() + runner.set_stream(True) + unittest.main(testRunner=runner) + else: + print('# tap.runner not available, using simple TAP output') + print('1..1') + program = unittest.main(exit=False) + if program.result.wasSuccessful(): + print('ok 1 - %r' % program.result) + else: + print('not ok 1 - %r' % program.result) + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh b/codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh new file mode 100755 index 00000000000..de38b977fdd --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +srcd=$(cd $(dirname "$0") && pwd) +. "${srcd}/libtest.sh" + +echo "1..1" + +# This test needs user namespaces +if test -n "${bwrap_is_suid:-}"; then + echo "ok - # SKIP no setuid support for --unshare-user" +else + mkfifo donepipe + $RUN --info-fd 42 --unshare-user --unshare-pid sh -c 'readlink /proc/self/ns/pid > sandbox-pidns; cat < donepipe' >/dev/null 42>info.json & + while ! test -f sandbox-pidns; do sleep 1; done + SANDBOX1PID=$(extract_child_pid info.json) + + ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 \ + $RUN --userns 11 --pidns 12 readlink /proc/self/ns/pid > sandbox2-pidns 11< /proc/$SANDBOX1PID/ns/user 12< /proc/$SANDBOX1PID/ns/pid + echo foo > donepipe + + assert_files_equal sandbox-pidns sandbox2-pidns + + rm donepipe info.json sandbox-pidns + + echo "ok - Test --pidns" +fi diff --git a/codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh b/codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh new file mode 100755 index 00000000000..07c5b4a3476 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +srcd=$(cd $(dirname "$0") && pwd) +. "${srcd}/libtest.sh" + +echo "1..1" + +# This test needs user namespaces +if test -n "${bwrap_is_suid:-}"; then + echo "ok - # SKIP no setuid support for --unshare-user" +else + mkfifo donepipe + + $RUN --info-fd 42 --unshare-user sh -c 'readlink /proc/self/ns/user > sandbox-userns; cat < donepipe' >/dev/null 42>info.json & + while ! test -f sandbox-userns; do sleep 1; done + SANDBOX1PID=$(extract_child_pid info.json) + + $RUN --userns 11 readlink /proc/self/ns/user > sandbox2-userns 11< /proc/$SANDBOX1PID/ns/user + echo foo > donepipe + + assert_files_equal sandbox-userns sandbox2-userns + + rm donepipe info.json sandbox-userns + + echo "ok - Test --userns" +fi diff --git a/codex-rs/vendor/bubblewrap/tests/test-utils.c b/codex-rs/vendor/bubblewrap/tests/test-utils.c new file mode 100644 index 00000000000..64f42ff18c0 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/test-utils.c @@ -0,0 +1,247 @@ +/* + * Copyright © 2019-2021 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include +#include +#include + +#include "utils.h" + +/* A small implementation of TAP */ +static unsigned int test_number = 0; + +__attribute__((format(printf, 1, 2))) +static void +ok (const char *format, ...) +{ + va_list ap; + + printf ("ok %u - ", ++test_number); + va_start (ap, format); + vprintf (format, ap); + va_end (ap); + printf ("\n"); +} + +/* for simplicity we always die immediately on failure */ +#define not_ok(fmt, ...) die (fmt, ## __VA_ARGS__) + +/* approximately GLib-compatible helper macros */ +#define g_test_message(fmt, ...) printf ("# " fmt "\n", ## __VA_ARGS__) +#define g_assert_cmpstr(left_expr, op, right_expr) \ + do { \ + const char *left = (left_expr); \ + const char *right = (right_expr); \ + if (strcmp0 (left, right) op 0) \ + ok ("%s (\"%s\") %s %s (\"%s\")", #left_expr, left, #op, #right_expr, right); \ + else \ + not_ok ("expected %s (\"%s\") %s %s (\"%s\")", \ + #left_expr, left, #op, #right_expr, right); \ + } while (0) +#define g_assert_cmpint(left_expr, op, right_expr) \ + do { \ + intmax_t left = (left_expr); \ + intmax_t right = (right_expr); \ + if (left op right) \ + ok ("%s (%ji) %s %s (%ji)", #left_expr, left, #op, #right_expr, right); \ + else \ + not_ok ("expected %s (%ji) %s %s (%ji)", \ + #left_expr, left, #op, #right_expr, right); \ + } while (0) +#define g_assert_cmpuint(left_expr, op, right_expr) \ + do { \ + uintmax_t left = (left_expr); \ + uintmax_t right = (right_expr); \ + if (left op right) \ + ok ("%s (%ju) %s %s (%ju)", #left_expr, left, #op, #right_expr, right); \ + else \ + not_ok ("expected %s (%ju) %s %s (%ju)", \ + #left_expr, left, #op, #right_expr, right); \ + } while (0) +#define g_assert_true(expr) \ + do { \ + if ((expr)) \ + ok ("%s", #expr); \ + else \ + not_ok ("expected %s to be true", #expr); \ + } while (0) +#define g_assert_false(expr) \ + do { \ + if (!(expr)) \ + ok ("!(%s)", #expr); \ + else \ + not_ok ("expected %s to be false", #expr); \ + } while (0) +#define g_assert_null(expr) \ + do { \ + if ((expr) == NULL) \ + ok ("%s was null", #expr); \ + else \ + not_ok ("expected %s to be null", #expr); \ + } while (0) +#define g_assert_nonnull(expr) \ + do { \ + if ((expr) != NULL) \ + ok ("%s wasn't null", #expr); \ + else \ + not_ok ("expected %s to be non-null", #expr); \ + } while (0) + +static int +strcmp0 (const char *left, + const char *right) +{ + if (left == right) + return 0; + + if (left == NULL) + return -1; + + if (right == NULL) + return 1; + + return strcmp (left, right); +} + +static void +test_n_elements (void) +{ + int three[] = { 1, 2, 3 }; + g_assert_cmpuint (N_ELEMENTS (three), ==, 3); +} + +static void +test_strconcat (void) +{ + const char *a = "aaa"; + const char *b = "bbb"; + char *ab = strconcat (a, b); + g_assert_cmpstr (ab, ==, "aaabbb"); + free (ab); +} + +static void +test_strconcat3 (void) +{ + const char *a = "aaa"; + const char *b = "bbb"; + const char *c = "ccc"; + char *abc = strconcat3 (a, b, c); + g_assert_cmpstr (abc, ==, "aaabbbccc"); + free (abc); +} + +static void +test_has_prefix (void) +{ + g_assert_true (has_prefix ("foo", "foo")); + g_assert_true (has_prefix ("foobar", "foo")); + g_assert_false (has_prefix ("foobar", "fool")); + g_assert_false (has_prefix ("foo", "fool")); + g_assert_true (has_prefix ("foo", "")); + g_assert_true (has_prefix ("", "")); + g_assert_false (has_prefix ("", "no")); + g_assert_false (has_prefix ("yes", "no")); +} + +static void +test_has_path_prefix (void) +{ + static const struct + { + const char *str; + const char *prefix; + bool expected; + } tests[] = + { + { "/run/host/usr", "/run/host", true }, + { "/run/host/usr", "/run/host/", true }, + { "/run/host", "/run/host", true }, + { "////run///host////usr", "//run//host", true }, + { "////run///host////usr", "//run//host////", true }, + { "/run/hostage", "/run/host", false }, + /* Any number of leading slashes is ignored, even zero */ + { "foo/bar", "/foo", true }, + { "/foo/bar", "foo", true }, + }; + size_t i; + + for (i = 0; i < N_ELEMENTS (tests); i++) + { + const char *str = tests[i].str; + const char *prefix = tests[i].prefix; + bool expected = tests[i].expected; + + if (expected) + g_test_message ("%s should have path prefix %s", str, prefix); + else + g_test_message ("%s should not have path prefix %s", str, prefix); + + if (expected) + g_assert_true (has_path_prefix (str, prefix)); + else + g_assert_false (has_path_prefix (str, prefix)); + } +} + +static void +test_string_builder (void) +{ + StringBuilder sb = {0}; + + strappend (&sb, "aaa"); + g_assert_cmpstr (sb.str, ==, "aaa"); + strappend (&sb, "bbb"); + g_assert_cmpstr (sb.str, ==, "aaabbb"); + strappendf (&sb, "c%dc%s", 9, "x"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx"); + strappend_escape_for_mount_options (&sb, "/path :,\\"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\"); + strappend (&sb, "zzz"); + g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\zzz"); + + free (sb.str); + sb = (StringBuilder){0}; + + strappend_escape_for_mount_options (&sb, "aaa"); + g_assert_cmpstr (sb.str, ==, "aaa"); + + free (sb.str); + sb = (StringBuilder){0}; + + strappend_escape_for_mount_options (&sb, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + g_assert_cmpstr (sb.str, ==, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + free (sb.str); +} + +int +main (int argc UNUSED, + char **argv UNUSED) +{ + setvbuf (stdout, NULL, _IONBF, 0); + test_n_elements (); + test_strconcat (); + test_strconcat3 (); + test_has_prefix (); + test_has_path_prefix (); + test_string_builder (); + printf ("1..%u\n", test_number); + return 0; +} diff --git a/codex-rs/vendor/bubblewrap/tests/try-syscall.c b/codex-rs/vendor/bubblewrap/tests/try-syscall.c new file mode 100644 index 00000000000..6f2f112c6ec --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/try-syscall.c @@ -0,0 +1,180 @@ +/* + * Copyright 2021 Simon McVittie + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * Try one or more system calls that might have been blocked by a + * seccomp filter. Return the last value of errno seen. + * + * In general, we pass a bad fd or pointer to each syscall that will + * accept one, so that it will fail with EBADF or EFAULT without side-effects. + * + * This helper is used for regression tests in both bubblewrap and flatpak. + * Please keep both copies in sync. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MIPS_SIM) +# if _MIPS_SIM == _ABIO32 +# define MISSING_SYSCALL_BASE 4000 +# elif _MIPS_SIM == _ABI64 +# define MISSING_SYSCALL_BASE 5000 +# elif _MIPS_SIM == _ABIN32 +# define MISSING_SYSCALL_BASE 6000 +# else +# error "Unknown MIPS ABI" +# endif +#endif + +#if defined(__ia64__) +# define MISSING_SYSCALL_BASE 1024 +#endif + +#if defined(__alpha__) +# define MISSING_SYSCALL_BASE 110 +#endif + +#if defined(__x86_64__) && defined(__ILP32__) +# define MISSING_SYSCALL_BASE 0x40000000 +#endif + +/* + * MISSING_SYSCALL_BASE: + * + * Number to add to the syscall numbers of recently-added syscalls + * to get the appropriate syscall for the current ABI. + */ +#ifndef MISSING_SYSCALL_BASE +# define MISSING_SYSCALL_BASE 0 +#endif + +#ifndef __NR_clone3 +# define __NR_clone3 (MISSING_SYSCALL_BASE + 435) +#endif + +/* + * The size of clone3's parameter (as of 2021) + */ +#define SIZEOF_STRUCT_CLONE_ARGS ((size_t) 88) + +/* + * An invalid pointer that will cause syscalls to fail with EFAULT + */ +#define WRONG_POINTER ((char *) 1) + +#ifndef PR_GET_CHILD_SUBREAPER +#define PR_GET_CHILD_SUBREAPER 37 +#endif + +int +main (int argc, char **argv) +{ + int errsv = 0; + int i; + + for (i = 1; i < argc; i++) + { + const char *arg = argv[i]; + + if (strcmp (arg, "print-errno-values") == 0) + { + printf ("EBADF=%d\n", EBADF); + printf ("EFAULT=%d\n", EFAULT); + printf ("ENOENT=%d\n", ENOENT); + printf ("ENOSYS=%d\n", ENOSYS); + printf ("EPERM=%d\n", EPERM); + } + else if (strcmp (arg, "chmod") == 0) + { + /* If not blocked by seccomp, this will fail with EFAULT */ + if (chmod (WRONG_POINTER, 0700) != 0) + { + errsv = errno; + perror (arg); + } + } + else if (strcmp (arg, "chroot") == 0) + { + /* If not blocked by seccomp, this will fail with EFAULT */ + if (chroot (WRONG_POINTER) != 0) + { + errsv = errno; + perror (arg); + } + } + else if (strcmp (arg, "clone3") == 0) + { + /* If not blocked by seccomp, this will fail with EFAULT */ + if (syscall (__NR_clone3, WRONG_POINTER, SIZEOF_STRUCT_CLONE_ARGS) != 0) + { + errsv = errno; + perror (arg); + } + } + else if (strcmp (arg, "ioctl TIOCNOTTY") == 0) + { + /* If not blocked by seccomp, this will fail with EBADF */ + if (ioctl (-1, TIOCNOTTY) != 0) + { + errsv = errno; + perror (arg); + } + } + else if (strcmp (arg, "ioctl TIOCSTI") == 0) + { + /* If not blocked by seccomp, this will fail with EBADF */ + if (ioctl (-1, TIOCSTI, WRONG_POINTER) != 0) + { + errsv = errno; + perror (arg); + } + } +#ifdef __LP64__ + else if (strcmp (arg, "ioctl TIOCSTI CVE-2019-10063") == 0) + { + unsigned long not_TIOCSTI = (0x123UL << 32) | (unsigned long) TIOCSTI; + + /* If not blocked by seccomp, this will fail with EBADF */ + if (syscall (__NR_ioctl, -1, not_TIOCSTI, WRONG_POINTER) != 0) + { + errsv = errno; + perror (arg); + } + } +#endif + else if (strcmp (arg, "listen") == 0) + { + /* If not blocked by seccomp, this will fail with EBADF */ + if (listen (-1, 42) != 0) + { + errsv = errno; + perror (arg); + } + } + else if (strcmp (arg, "prctl") == 0) + { + /* If not blocked by seccomp, this will fail with EFAULT */ + if (prctl (PR_GET_CHILD_SUBREAPER, WRONG_POINTER, 0, 0, 0) != 0) + { + errsv = errno; + perror (arg); + } + } + else + { + fprintf (stderr, "Unsupported syscall \"%s\"\n", arg); + errsv = ENOENT; + } + } + + return errsv; +} diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore new file mode 100644 index 00000000000..371a7d97bbe --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore @@ -0,0 +1,2 @@ +/_build/ +/subprojects/ diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/README b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/README new file mode 100644 index 00000000000..97d2e88eb17 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/README @@ -0,0 +1,3 @@ +This is a simple example of a project that uses bubblewrap as a +subproject. The intention is that if this project can successfully build +bubblewrap as a subproject, then so could Flatpak. diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py new file mode 100755 index 00000000000..10b0947ca69 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# Copyright 2022 Collabora Ltd. +# SPDX-License-Identifier: LGPL-2.0-or-later + +import subprocess +import sys + +if __name__ == '__main__': + completed = subprocess.run( + ['objdump', '-T', '-x', sys.argv[1]], + stdout=subprocess.PIPE, + ) + stdout = completed.stdout + assert stdout is not None + seen_rpath = False + + for line in stdout.splitlines(): + words = line.strip().split() + + if words and words[0] in (b'RPATH', b'RUNPATH'): + print(line.decode(errors='backslashreplace')) + assert len(words) == 2, words + assert words[1] == b'${ORIGIN}/../lib', words + seen_rpath = True + + assert seen_rpath diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h new file mode 100644 index 00000000000..4a99af4ddba --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h @@ -0,0 +1 @@ +#error Should not use superproject config.h to compile bubblewrap diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in new file mode 100644 index 00000000000..1d1e56a8c6b --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in @@ -0,0 +1 @@ +#error Should not use superproject generated config.h to compile bubblewrap diff --git a/codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build new file mode 100644 index 00000000000..bc4781cd0ee --- /dev/null +++ b/codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build @@ -0,0 +1,20 @@ +project( + 'use-bubblewrap-as-subproject', + 'c', + version : '0', + meson_version : '>=0.49.0', +) + +configure_file( + output : 'config.h', + input : 'dummy-config.h.in', + configuration : configuration_data(), +) + +subproject( + 'bubblewrap', + default_options : [ + 'install_rpath=${ORIGIN}/../lib', + 'program_prefix=not-flatpak-', + ], +) diff --git a/codex-rs/vendor/bubblewrap/uncrustify.cfg b/codex-rs/vendor/bubblewrap/uncrustify.cfg new file mode 100644 index 00000000000..6d3ad56518d --- /dev/null +++ b/codex-rs/vendor/bubblewrap/uncrustify.cfg @@ -0,0 +1,136 @@ +newlines lf + +input_tab_size 8 +output_tab_size 8 + +string_escape_char 92 +string_escape_char2 0 + +# indenting +indent_columns 2 +indent_with_tabs 0 +indent_align_string True +indent_brace 2 +indent_braces false +indent_braces_no_func True +indent_func_call_param false +indent_func_def_param false +indent_func_proto_param false +indent_switch_case 0 +indent_case_brace 2 +indent_paren_close 1 + +# spacing +sp_arith Add +sp_assign Add +sp_enum_assign Add +sp_bool Add +sp_compare Add +sp_inside_paren Remove +sp_inside_fparens Remove +sp_func_def_paren Force +sp_func_proto_paren Force +sp_paren_paren Remove +sp_balance_nested_parens False +sp_paren_brace Remove +sp_before_square Remove +sp_before_squares Remove +sp_inside_square Remove +sp_before_ptr_star Add +sp_between_ptr_star Remove +sp_after_comma Add +sp_before_comma Remove +sp_after_cast Add +sp_sizeof_paren Add +sp_not Remove +sp_inv Remove +sp_addr Remove +sp_member Remove +sp_deref Remove +sp_sign Remove +sp_incdec Remove +sp_attribute_paren remove +sp_macro Force +sp_func_call_paren Force +sp_func_call_user_paren Remove +set func_call_user _ N_ C_ g_autoptr g_auto +sp_brace_typedef add +sp_cond_colon add +sp_cond_question add +sp_defined_paren remove + +# alignment +align_keep_tabs False +align_with_tabs False +align_on_tabstop False +align_number_left True +align_func_params True +align_var_def_span 0 +align_var_def_amp_style 1 +align_var_def_colon true +align_enum_equ_span 0 +align_var_struct_span 2 +align_var_def_star_style 2 +align_var_def_amp_style 2 +align_typedef_span 2 +align_typedef_func 0 +align_typedef_star_style 2 +align_typedef_amp_style 2 + +# newlines +nl_assign_leave_one_liners True +nl_enum_leave_one_liners False +nl_func_leave_one_liners False +nl_if_leave_one_liners False +nl_end_of_file Add +nl_assign_brace Remove +nl_func_var_def_blk 1 +nl_fcall_brace Add +nl_enum_brace Remove +nl_struct_brace Force +nl_union_brace Force +nl_if_brace Force +nl_brace_else Force +nl_elseif_brace Force +nl_else_brace Add +nl_for_brace Force +nl_while_brace Force +nl_do_brace Force +nl_brace_while Force +nl_switch_brace Force +nl_before_case True +nl_after_case False +nl_func_type_name Force +nl_func_proto_type_name Remove +nl_func_paren Remove +nl_func_decl_start Remove +nl_func_decl_args Force +nl_func_decl_end Remove +nl_fdef_brace Force +nl_after_return False +nl_define_macro False +nl_create_if_one_liner False +nl_create_for_one_liner False +nl_create_while_one_liner False +nl_after_semicolon True +nl_multi_line_cond true + +# mod +# I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike +# Not clear what to do about that... +mod_full_brace_for Remove +mod_full_brace_if Remove +mod_full_brace_if_chain True +mod_full_brace_while Remove +mod_full_brace_do Remove +mod_full_brace_nl 3 +mod_paren_on_return Remove + +# line splitting +#code_width = 78 +ls_for_split_full True +ls_func_split_full True + +# positioning +pos_bool Trail +pos_conditional Trail diff --git a/codex-rs/vendor/bubblewrap/uncrustify.sh b/codex-rs/vendor/bubblewrap/uncrustify.sh new file mode 100755 index 00000000000..7c9080b1995 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/uncrustify.sh @@ -0,0 +1,2 @@ +#!/bin/sh +uncrustify -c uncrustify.cfg --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$` diff --git a/codex-rs/vendor/bubblewrap/utils.c b/codex-rs/vendor/bubblewrap/utils.c new file mode 100644 index 00000000000..51875aea9a1 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/utils.c @@ -0,0 +1,1080 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ +#include "config.h" + +#include "utils.h" +#include +#include +#include +#include +#include +#ifdef HAVE_SELINUX +#include +#endif + +#ifndef HAVE_SELINUX_2_3 +/* libselinux older than 2.3 weren't const-correct */ +#define setexeccon(x) setexeccon ((security_context_t) x) +#define setfscreatecon(x) setfscreatecon ((security_context_t) x) +#define security_check_context(x) security_check_context ((security_context_t) x) +#endif + +bool bwrap_level_prefix = false; + +__attribute__((format(printf, 2, 0))) static void +bwrap_logv (int severity, + const char *format, + va_list args, + const char *detail) +{ + if (bwrap_level_prefix) + fprintf (stderr, "<%d>", severity); + + fprintf (stderr, "bwrap: "); + vfprintf (stderr, format, args); + + if (detail != NULL) + fprintf (stderr, ": %s", detail); + + fprintf (stderr, "\n"); +} + +void +bwrap_log (int severity, + const char *format, ...) +{ + va_list args; + + va_start (args, format); + bwrap_logv (severity, format, args, NULL); + va_end (args); +} + +void +die_with_error (const char *format, ...) +{ + va_list args; + int errsv; + + errsv = errno; + + va_start (args, format); + bwrap_logv (LOG_ERR, format, args, strerror (errsv)); + va_end (args); + + exit (1); +} + +void +die_with_mount_error (const char *format, ...) +{ + va_list args; + int errsv; + + errsv = errno; + + va_start (args, format); + bwrap_logv (LOG_ERR, format, args, mount_strerror (errsv)); + va_end (args); + + exit (1); +} + +void +die (const char *format, ...) +{ + va_list args; + + va_start (args, format); + bwrap_logv (LOG_ERR, format, args, NULL); + va_end (args); + + exit (1); +} + +void +die_unless_label_valid (UNUSED const char *label) +{ +#ifdef HAVE_SELINUX + if (is_selinux_enabled () == 1) + { + if (security_check_context (label) < 0) + die_with_error ("invalid label %s", label); + return; + } +#endif + die ("labeling not supported on this system"); +} + +void +die_oom (void) +{ + fputs ("Out of memory\n", stderr); + exit (1); +} + +/* Fork, return in child, exiting the previous parent */ +void +fork_intermediate_child (void) +{ + int pid = fork (); + if (pid == -1) + die_with_error ("Can't fork for --pidns"); + + /* Parent is an process not needed */ + if (pid != 0) + exit (0); +} + +void * +xmalloc (size_t size) +{ + void *res = malloc (size); + + if (res == NULL) + die_oom (); + return res; +} + +void * +xcalloc (size_t nmemb, size_t size) +{ + void *res = calloc (nmemb, size); + + if (res == NULL) + die_oom (); + return res; +} + +void * +xrealloc (void *ptr, size_t size) +{ + void *res; + + assert (size != 0); + + res = realloc (ptr, size); + + if (res == NULL) + die_oom (); + return res; +} + +char * +xstrdup (const char *str) +{ + char *res; + + assert (str != NULL); + + res = strdup (str); + if (res == NULL) + die_oom (); + + return res; +} + +void +strfreev (char **str_array) +{ + if (str_array) + { + int i; + + for (i = 0; str_array[i] != NULL; i++) + free (str_array[i]); + + free (str_array); + } +} + +/* Compares if str has a specific path prefix. This differs + from a regular prefix in two ways. First of all there may + be multiple slashes separating the path elements, and + secondly, if a prefix is matched that has to be en entire + path element. For instance /a/prefix matches /a/prefix/foo/bar, + but not /a/prefixfoo/bar. */ +bool +has_path_prefix (const char *str, + const char *prefix) +{ + while (true) + { + /* Skip consecutive slashes to reach next path + element */ + while (*str == '/') + str++; + while (*prefix == '/') + prefix++; + + /* No more prefix path elements? Done! */ + if (*prefix == 0) + return true; + + /* Compare path element */ + while (*prefix != 0 && *prefix != '/') + { + if (*str != *prefix) + return false; + str++; + prefix++; + } + + /* Matched prefix path element, + must be entire str path element */ + if (*str != '/' && *str != 0) + return false; + } +} + +bool +path_equal (const char *path1, + const char *path2) +{ + while (true) + { + /* Skip consecutive slashes to reach next path + element */ + while (*path1 == '/') + path1++; + while (*path2 == '/') + path2++; + + /* No more prefix path elements? Done! */ + if (*path1 == 0 || *path2 == 0) + return *path1 == 0 && *path2 == 0; + + /* Compare path element */ + while (*path1 != 0 && *path1 != '/') + { + if (*path1 != *path2) + return false; + path1++; + path2++; + } + + /* Matched path1 path element, must be entire path element */ + if (*path2 != '/' && *path2 != 0) + return false; + } +} + + +bool +has_prefix (const char *str, + const char *prefix) +{ + return strncmp (str, prefix, strlen (prefix)) == 0; +} + +void +xclearenv (void) +{ + if (clearenv () != 0) + die_with_error ("clearenv failed"); +} + +void +xsetenv (const char *name, const char *value, int overwrite) +{ + if (setenv (name, value, overwrite)) + die ("setenv failed"); +} + +void +xunsetenv (const char *name) +{ + if (unsetenv (name)) + die ("unsetenv failed"); +} + +char * +strconcat (const char *s1, + const char *s2) +{ + size_t len = 0; + char *res; + + if (s1) + len += strlen (s1); + if (s2) + len += strlen (s2); + + res = xmalloc (len + 1); + *res = 0; + if (s1) + strcat (res, s1); + if (s2) + strcat (res, s2); + + return res; +} + +char * +strconcat3 (const char *s1, + const char *s2, + const char *s3) +{ + size_t len = 0; + char *res; + + if (s1) + len += strlen (s1); + if (s2) + len += strlen (s2); + if (s3) + len += strlen (s3); + + res = xmalloc (len + 1); + *res = 0; + if (s1) + strcat (res, s1); + if (s2) + strcat (res, s2); + if (s3) + strcat (res, s3); + + return res; +} + +char * +xasprintf (const char *format, + ...) +{ + char *buffer = NULL; + va_list args; + + va_start (args, format); + if (vasprintf (&buffer, format, args) == -1) + die_oom (); + va_end (args); + + return buffer; +} + +int +fdwalk (int proc_fd, int (*cb)(void *data, + int fd), void *data) +{ + int open_max; + int fd; + int dfd; + int res = 0; + DIR *d; + + dfd = TEMP_FAILURE_RETRY (openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY)); + if (dfd == -1) + return res; + + if ((d = fdopendir (dfd))) + { + struct dirent *de; + + while ((de = readdir (d))) + { + long l; + char *e = NULL; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + l = strtol (de->d_name, &e, 10); + if (errno != 0 || !e || *e) + continue; + + fd = (int) l; + + if ((long) fd != l) + continue; + + if (fd == dirfd (d)) + continue; + + if ((res = cb (data, fd)) != 0) + break; + } + + closedir (d); + return res; + } + + open_max = sysconf (_SC_OPEN_MAX); + + for (fd = 0; fd < open_max; fd++) + if ((res = cb (data, fd)) != 0) + break; + + return res; +} + +/* Sets errno on error (!= 0), ENOSPC on short write */ +int +write_to_fd (int fd, + const char *content, + ssize_t len) +{ + ssize_t res; + + while (len > 0) + { + res = write (fd, content, len); + if (res < 0 && errno == EINTR) + continue; + if (res <= 0) + { + if (res == 0) /* Unexpected short write, should not happen when writing to a file */ + errno = ENOSPC; + return -1; + } + len -= res; + content += res; + } + + return 0; +} + +/* Sets errno on error (!= 0), ENOSPC on short write */ +int +write_file_at (int dfd, + const char *path, + const char *content) +{ + int fd; + bool res; + int errsv; + + fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_RDWR | O_CLOEXEC, 0)); + if (fd == -1) + return -1; + + res = 0; + if (content) + res = write_to_fd (fd, content, strlen (content)); + + errsv = errno; + close (fd); + errno = errsv; + + return res; +} + +/* Sets errno on error (!= 0), ENOSPC on short write */ +int +create_file (const char *path, + mode_t mode, + const char *content) +{ + int fd; + int res; + int errsv; + + fd = TEMP_FAILURE_RETRY (creat (path, mode)); + if (fd == -1) + return -1; + + res = 0; + if (content) + res = write_to_fd (fd, content, strlen (content)); + + errsv = errno; + close (fd); + errno = errsv; + + return res; +} + +int +ensure_file (const char *path, + mode_t mode) +{ + struct stat buf; + + /* We check this ahead of time, otherwise + the create file will fail in the read-only + case with EROFS instead of EEXIST. + + We're trying to set up a mount point for a non-directory, so any + non-directory, non-symlink is acceptable - it doesn't necessarily + have to be a regular file. */ + if (stat (path, &buf) == 0 && + !S_ISDIR (buf.st_mode) && + !S_ISLNK (buf.st_mode)) + return 0; + + if (create_file (path, mode, NULL) != 0 && errno != EEXIST) + return -1; + + return 0; +} + + +#define BUFSIZE 8192 +/* Sets errno on error (!= 0), ENOSPC on short write */ +int +copy_file_data (int sfd, + int dfd) +{ + char buffer[BUFSIZE]; + ssize_t bytes_read; + + while (true) + { + bytes_read = read (sfd, buffer, BUFSIZE); + if (bytes_read == -1) + { + if (errno == EINTR) + continue; + + return -1; + } + + if (bytes_read == 0) + break; + + if (write_to_fd (dfd, buffer, bytes_read) != 0) + return -1; + } + + return 0; +} + +/* Sets errno on error (!= 0), ENOSPC on short write */ +int +copy_file (const char *src_path, + const char *dst_path, + mode_t mode) +{ + int sfd; + int dfd; + int res; + int errsv; + + sfd = TEMP_FAILURE_RETRY (open (src_path, O_CLOEXEC | O_RDONLY)); + if (sfd == -1) + return -1; + + dfd = TEMP_FAILURE_RETRY (creat (dst_path, mode)); + if (dfd == -1) + { + errsv = errno; + close (sfd); + errno = errsv; + return -1; + } + + res = copy_file_data (sfd, dfd); + + errsv = errno; + close (sfd); + close (dfd); + errno = errsv; + + return res; +} + +/* Sets errno on error (== NULL), + * Always ensures terminating zero */ +char * +load_file_data (int fd, + size_t *size) +{ + cleanup_free char *data = NULL; + ssize_t data_read; + ssize_t data_len; + ssize_t res; + + data_read = 0; + data_len = 4080; + data = xmalloc (data_len); + + do + { + if (data_len == data_read + 1) + { + if (data_len > SSIZE_MAX / 2) + { + errno = EFBIG; + return NULL; + } + + data_len *= 2; + data = xrealloc (data, data_len); + } + + do + res = read (fd, data + data_read, data_len - data_read - 1); + while (res < 0 && errno == EINTR); + + if (res < 0) + return NULL; + + data_read += res; + } + while (res > 0); + + data[data_read] = 0; + + if (size) + *size = (size_t) data_read; + + return steal_pointer (&data); +} + +/* Sets errno on error (== NULL), + * Always ensures terminating zero */ +char * +load_file_at (int dfd, + const char *path) +{ + int fd; + char *data; + int errsv; + + fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_CLOEXEC | O_RDONLY)); + if (fd == -1) + return NULL; + + data = load_file_data (fd, NULL); + + errsv = errno; + close (fd); + errno = errsv; + + return data; +} + +/* Sets errno on error (< 0) */ +int +get_file_mode (const char *pathname) +{ + struct stat buf; + + if (stat (pathname, &buf) != 0) + return -1; + + return buf.st_mode & S_IFMT; +} + +int +ensure_dir (const char *path, + mode_t mode) +{ + struct stat buf; + + /* We check this ahead of time, otherwise + the mkdir call can fail in the read-only + case with EROFS instead of EEXIST on some + filesystems (such as NFS) */ + if (stat (path, &buf) == 0) + { + if (!S_ISDIR (buf.st_mode)) + { + errno = ENOTDIR; + return -1; + } + + return 0; + } + + if (mkdir (path, mode) == -1 && errno != EEXIST) + return -1; + + return 0; +} + + +/* Sets errno on error (!= 0) */ +int +mkdir_with_parents (const char *pathname, + mode_t mode, + bool create_last) +{ + cleanup_free char *fn = NULL; + char *p; + + if (pathname == NULL || *pathname == '\0') + { + errno = EINVAL; + return -1; + } + + fn = xstrdup (pathname); + + p = fn; + while (*p == '/') + p++; + + do + { + while (*p && *p != '/') + p++; + + if (!*p) + p = NULL; + else + *p = '\0'; + + if (!create_last && p == NULL) + break; + + if (ensure_dir (fn, mode) != 0) + return -1; + + if (p) + { + *p++ = '/'; + while (*p && *p == '/') + p++; + } + } + while (p); + + return 0; +} + +/* Send an ucred with current pid/uid/gid over a socket, it can be + read back with read_pid_from_socket(), and then the kernel has + translated it between namespaces as needed. */ +void +send_pid_on_socket (int sockfd) +{ + char buf[1] = { 0 }; + struct msghdr msg = {}; + struct iovec iov = { buf, sizeof (buf) }; + const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred)); + _Alignas(struct cmsghdr) char control_buf_snd[control_len_snd]; + struct cmsghdr *cmsg; + struct ucred cred; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf_snd; + msg.msg_controllen = control_len_snd; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + + cred.pid = getpid (); + cred.uid = geteuid (); + cred.gid = getegid (); + memcpy (CMSG_DATA (cmsg), &cred, sizeof (cred)); + + if (TEMP_FAILURE_RETRY (sendmsg (sockfd, &msg, 0)) < 0) + die_with_error ("Can't send pid"); +} + +void +create_pid_socketpair (int sockets[2]) +{ + int enable = 1; + + if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) + die_with_error ("Can't create intermediate pids socket"); + + if (setsockopt (sockets[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof (enable)) < 0) + die_with_error ("Can't set SO_PASSCRED"); +} + +int +read_pid_from_socket (int sockfd) +{ + char recv_buf[1] = { 0 }; + struct msghdr msg = {}; + struct iovec iov = { recv_buf, sizeof (recv_buf) }; + const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred)); + _Alignas(struct cmsghdr) char control_buf_rcv[control_len_rcv]; + struct cmsghdr* cmsg; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf_rcv; + msg.msg_controllen = control_len_rcv; + + if (TEMP_FAILURE_RETRY (recvmsg (sockfd, &msg, 0)) < 0) + die_with_error ("Can't read pid from socket"); + + if (msg.msg_controllen <= 0) + die ("Unexpected short read from pid socket"); + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + payload_len == sizeof(struct ucred)) + { + struct ucred cred; + + memcpy (&cred, CMSG_DATA (cmsg), sizeof (cred)); + return cred.pid; + } + } + die ("No pid returned on socket"); +} + +/* Sets errno on error (== NULL), + * Always ensures terminating zero */ +char * +readlink_malloc (const char *pathname) +{ + size_t size = 50; + ssize_t n; + cleanup_free char *value = NULL; + + do + { + if (size > SIZE_MAX / 2) + die ("Symbolic link target pathname too long"); + size *= 2; + value = xrealloc (value, size); + n = readlink (pathname, value, size - 1); + if (n < 0) + return NULL; + } + while (size - 2 < (size_t)n); + + value[n] = 0; + return steal_pointer (&value); +} + +char * +get_oldroot_path (const char *path) +{ + while (*path == '/') + path++; + return strconcat ("/oldroot/", path); +} + +char * +get_newroot_path (const char *path) +{ + while (*path == '/') + path++; + return strconcat ("/newroot/", path); +} + +int +raw_clone (unsigned long flags, + void *child_stack) +{ +#if defined(__s390__) || defined(__CRIS__) + /* On s390 and cris the order of the first and second arguments + * of the raw clone() system call is reversed. */ + return (int) syscall (__NR_clone, child_stack, flags); +#else + return (int) syscall (__NR_clone, flags, child_stack); +#endif +} + +int +pivot_root (const char * new_root, const char * put_old) +{ +#ifdef __NR_pivot_root + return syscall (__NR_pivot_root, new_root, put_old); +#else + errno = ENOSYS; + return -1; +#endif +} + +char * +label_mount (const char *opt, UNUSED const char *mount_label) +{ +#ifdef HAVE_SELINUX + if (mount_label) + { + if (opt) + return xasprintf ("%s,context=\"%s\"", opt, mount_label); + else + return xasprintf ("context=\"%s\"", mount_label); + } +#endif + if (opt) + return xstrdup (opt); + return NULL; +} + +int +label_create_file (UNUSED const char *file_label) +{ +#ifdef HAVE_SELINUX + if (is_selinux_enabled () > 0 && file_label) + return setfscreatecon (file_label); +#endif + return 0; +} + +int +label_exec (UNUSED const char *exec_label) +{ +#ifdef HAVE_SELINUX + if (is_selinux_enabled () > 0 && exec_label) + return setexeccon (exec_label); +#endif + return 0; +} + +/* + * Like strerror(), but specialized for a failed mount(2) call. + */ +const char * +mount_strerror (int errsv) +{ + switch (errsv) + { + case ENOSPC: + /* "No space left on device" misleads users into thinking there + * is some sort of disk-space problem, but mount(2) uses that + * errno value to mean something more like "limit exceeded". */ + return ("Limit exceeded (ENOSPC). " + "(Hint: Check that /proc/sys/fs/mount-max is sufficient, " + "typically 100000)"); + + default: + return strerror (errsv); + } +} + +/* + * Return a + b if it would not overflow. + * Die with an "out of memory" error if it would. + */ +static size_t +xadd (size_t a, size_t b) +{ +#if defined(__GNUC__) && __GNUC__ >= 5 + size_t result; + if (__builtin_add_overflow (a, b, &result)) + die_oom (); + return result; +#else + if (a > SIZE_MAX - b) + die_oom (); + + return a + b; +#endif +} + +/* + * Return a * b if it would not overflow. + * Die with an "out of memory" error if it would. + */ +static size_t +xmul (size_t a, size_t b) +{ +#if defined(__GNUC__) && __GNUC__ >= 5 + size_t result; + if (__builtin_mul_overflow (a, b, &result)) + die_oom (); + return result; +#else + if (b != 0 && a > SIZE_MAX / b) + die_oom (); + + return a * b; +#endif +} + +void +strappend (StringBuilder *dest, const char *src) +{ + size_t len = strlen (src); + size_t new_offset = xadd (dest->offset, len); + + if (new_offset >= dest->size) + { + dest->size = xmul (xadd (new_offset, 1), 2); + dest->str = xrealloc (dest->str, dest->size); + } + + /* Preserves the invariant that dest->str is always null-terminated, even + * though the offset is positioned at the null byte for the next write. + */ + strncpy (dest->str + dest->offset, src, len + 1); + dest->offset = new_offset; +} + +__attribute__((format (printf, 2, 3))) +void +strappendf (StringBuilder *dest, const char *fmt, ...) +{ + va_list args; + int len; + size_t new_offset; + + va_start (args, fmt); + len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); + va_end (args); + if (len < 0) + die_with_error ("vsnprintf"); + new_offset = xadd (dest->offset, len); + if (new_offset >= dest->size) + { + dest->size = xmul (xadd (new_offset, 1), 2); + dest->str = xrealloc (dest->str, dest->size); + va_start (args, fmt); + len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); + va_end (args); + if (len < 0) + die_with_error ("vsnprintf"); + } + + dest->offset = new_offset; +} + +void +strappend_escape_for_mount_options (StringBuilder *dest, const char *src) +{ + bool unescaped = true; + + for (;;) + { + if (dest->offset == dest->size) + { + dest->size = MAX (64, xmul (dest->size, 2)); + dest->str = xrealloc (dest->str, dest->size); + } + switch (*src) + { + case '\0': + dest->str[dest->offset] = '\0'; + return; + + case '\\': + case ',': + case ':': + if (unescaped) + { + dest->str[dest->offset++] = '\\'; + unescaped = false; + continue; + } + /* else fall through */ + + default: + dest->str[dest->offset++] = *src; + unescaped = true; + break; + } + src++; + } +} diff --git a/codex-rs/vendor/bubblewrap/utils.h b/codex-rs/vendor/bubblewrap/utils.h new file mode 100644 index 00000000000..079fe7c9362 --- /dev/null +++ b/codex-rs/vendor/bubblewrap/utils.h @@ -0,0 +1,217 @@ +/* bubblewrap + * Copyright (C) 2016 Alexander Larsson + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#define debug(...) bwrap_log (LOG_DEBUG, __VA_ARGS__) +#else +#define debug(...) +#endif + +#define UNUSED __attribute__((__unused__)) + +#define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) + +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif + +#define PIPE_READ_END 0 +#define PIPE_WRITE_END 1 + +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + +extern bool bwrap_level_prefix; + +void bwrap_log (int severity, + const char *format, + ...) __attribute__((format (printf, 2, 3))); +#define warn(...) bwrap_log (LOG_WARNING, __VA_ARGS__) + +void die_with_error (const char *format, + ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); +void die_with_mount_error (const char *format, + ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); +void die (const char *format, + ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); +void die_oom (void) __attribute__((__noreturn__)); +void die_unless_label_valid (const char *label); + +void fork_intermediate_child (void); + +void *xmalloc (size_t size); +void *xcalloc (size_t nmemb, size_t size); +void *xrealloc (void *ptr, + size_t size); +char *xstrdup (const char *str); +void strfreev (char **str_array); +void xclearenv (void); +void xsetenv (const char *name, + const char *value, + int overwrite); +void xunsetenv (const char *name); +char *strconcat (const char *s1, + const char *s2); +char *strconcat3 (const char *s1, + const char *s2, + const char *s3); +char * xasprintf (const char *format, + ...) __attribute__((format (printf, 1, 2))); +bool has_prefix (const char *str, + const char *prefix); +bool has_path_prefix (const char *str, + const char *prefix); +bool path_equal (const char *path1, + const char *path2); +int fdwalk (int proc_fd, + int (*cb)(void *data, + int fd), + void *data); +char *load_file_data (int fd, + size_t *size); +char *load_file_at (int dirfd, + const char *path); +int write_file_at (int dirfd, + const char *path, + const char *content); +int write_to_fd (int fd, + const char *content, + ssize_t len); +int copy_file_data (int sfd, + int dfd); +int copy_file (const char *src_path, + const char *dst_path, + mode_t mode); +int create_file (const char *path, + mode_t mode, + const char *content); +int ensure_file (const char *path, + mode_t mode); +int ensure_dir (const char *path, + mode_t mode); +int get_file_mode (const char *pathname); +int mkdir_with_parents (const char *pathname, + mode_t mode, + bool create_last); +void create_pid_socketpair (int sockets[2]); +void send_pid_on_socket (int socket); +int read_pid_from_socket (int socket); +char *get_oldroot_path (const char *path); +char *get_newroot_path (const char *path); +char *readlink_malloc (const char *pathname); + +/* syscall wrappers */ +int raw_clone (unsigned long flags, + void *child_stack); +int pivot_root (const char *new_root, + const char *put_old); +char *label_mount (const char *opt, + const char *mount_label); +int label_exec (const char *exec_label); +int label_create_file (const char *file_label); + +const char *mount_strerror (int errsv); + +static inline void +cleanup_freep (void *p) +{ + void **pp = (void **) p; + + if (*pp) + free (*pp); +} + +static inline void +cleanup_strvp (void *p) +{ + void **pp = (void **) p; + + strfreev (*pp); +} + +static inline void +cleanup_fdp (int *fdp) +{ + int fd; + + assert (fdp); + + fd = *fdp; + if (fd != -1) + (void) close (fd); +} + +#define cleanup_free __attribute__((cleanup (cleanup_freep))) +#define cleanup_fd __attribute__((cleanup (cleanup_fdp))) +#define cleanup_strv __attribute__((cleanup (cleanup_strvp))) + +static inline void * +steal_pointer (void *pp) +{ + void **ptr = (void **) pp; + void *ref; + + ref = *ptr; + *ptr = NULL; + + return ref; +} + +/* type safety */ +#define steal_pointer(pp) \ + (0 ? (*(pp)) : (steal_pointer) (pp)) + +typedef struct _StringBuilder StringBuilder; + +struct _StringBuilder +{ + char * str; + size_t size; + size_t offset; +}; + +void strappend (StringBuilder *dest, + const char *src); +void strappendf (StringBuilder *dest, + const char *fmt, + ...); +void strappend_escape_for_mount_options (StringBuilder *dest, + const char *src); From 1ed1350aa94868295151153fe6dae09786a37354 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Fri, 30 Jan 2026 13:46:15 -0800 Subject: [PATCH 3/7] fix(linux-sandbox): rerun build.rs for vendored bwrap --- codex-rs/linux-sandbox/build.rs | 22 ++++++++++++++++++++ codex-rs/linux-sandbox/src/vendored_bwrap.rs | 12 +++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/codex-rs/linux-sandbox/build.rs b/codex-rs/linux-sandbox/build.rs index 95503badbe8..492ae3af7b9 100644 --- a/codex-rs/linux-sandbox/build.rs +++ b/codex-rs/linux-sandbox/build.rs @@ -5,6 +5,28 @@ use std::path::PathBuf; fn main() { // Tell rustc/clippy that this is an expected cfg value. println!("cargo:rustc-check-cfg=cfg(vendored_bwrap_available)"); + println!("cargo:rerun-if-env-changed=CODEX_BWRAP_ENABLE_FFI"); + println!("cargo:rerun-if-env-changed=CODEX_BWRAP_SOURCE_DIR"); + + // Rebuild if the vendored bwrap sources change. + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default()); + let vendor_dir = manifest_dir.join("../vendor/bubblewrap"); + println!( + "cargo:rerun-if-changed={}", + vendor_dir.join("bubblewrap.c").display() + ); + println!( + "cargo:rerun-if-changed={}", + vendor_dir.join("bind-mount.c").display() + ); + println!( + "cargo:rerun-if-changed={}", + vendor_dir.join("network.c").display() + ); + println!( + "cargo:rerun-if-changed={}", + vendor_dir.join("utils.c").display() + ); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); if target_os != "linux" { diff --git a/codex-rs/linux-sandbox/src/vendored_bwrap.rs b/codex-rs/linux-sandbox/src/vendored_bwrap.rs index 664450af5a9..ab4fb959ef2 100644 --- a/codex-rs/linux-sandbox/src/vendored_bwrap.rs +++ b/codex-rs/linux-sandbox/src/vendored_bwrap.rs @@ -39,10 +39,14 @@ mod imp { pub(crate) fn exec_vendored_bwrap(_argv: Vec) -> ! { panic!( "build-time bubblewrap is not available in this build.\n\ -To enable it on Linux:\n\ -- set CODEX_BWRAP_ENABLE_FFI=1\n\ -- and either set CODEX_BWRAP_SOURCE_DIR to a bubblewrap checkout,\n\ - or set CODEX_BWRAP_FETCH=1 (and optionally CODEX_BWRAP_FETCH_REF)." +Rebuild codex-linux-sandbox on Linux with CODEX_BWRAP_ENABLE_FFI=1.\n\ +Example:\n\ +- cd codex-rs && CODEX_BWRAP_ENABLE_FFI=1 cargo build -p codex-linux-sandbox\n\ +If this crate was already built without it, run:\n\ +- cargo clean -p codex-linux-sandbox\n\ +Notes:\n\ +- libcap headers must be available via pkg-config\n\ +- bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)" ); } } From 47bebe67cba02f309f97d63bc8f9066bdb55fb71 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Fri, 30 Jan 2026 16:02:38 -0800 Subject: [PATCH 4/7] fix(linux-sandbox): fully initialize sockaddr_nl --- codex-rs/vendor/bubblewrap/network.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-rs/vendor/bubblewrap/network.c b/codex-rs/vendor/bubblewrap/network.c index 373d606a0ab..106e6d6e363 100644 --- a/codex-rs/vendor/bubblewrap/network.c +++ b/codex-rs/vendor/bubblewrap/network.c @@ -50,7 +50,7 @@ static int rtnl_send_request (int rtnl_fd, struct nlmsghdr *header) { - struct sockaddr_nl dst_addr = { AF_NETLINK, 0 }; + struct sockaddr_nl dst_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0 }; ssize_t sent; sent = TEMP_FAILURE_RETRY (sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0, @@ -139,7 +139,7 @@ loopback_setup (void) int r, if_loopback; cleanup_fd int rtnl_fd = -1; char buffer[1024]; - struct sockaddr_nl src_addr = { AF_NETLINK, 0 }; + struct sockaddr_nl src_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 0 }; struct nlmsghdr *header; struct ifaddrmsg *addmsg; struct ifinfomsg *infomsg; From 876df3f09e96909eb15daaaf10b0aff3379a6db5 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 2 Feb 2026 13:31:39 -0800 Subject: [PATCH 5/7] chore(linux-sandbox): keep smoke script out of vendoring split --- .../scripts/test_linux_sandbox.sh | 191 ------------------ 1 file changed, 191 deletions(-) delete mode 100644 codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh diff --git a/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh b/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh deleted file mode 100644 index ba6da626174..00000000000 --- a/codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Linux sandbox smoke test script. -# -# This is designed for Linux devboxes where bwrap is available. It builds the -# codex-linux-sandbox binary and runs a small matrix of behavior checks: -# - workspace writes succeed -# - protected paths (.git, .codex) remain read-only -# - writes outside allowed roots fail -# - network_access=false blocks outbound sockets -# -# Usage: -# codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh -# -# Optional env vars: -# CODEX_LINUX_SANDBOX_NO_PROC=1 # default: 1 (pass --no-proc for bwrap suite) -# CODEX_LINUX_SANDBOX_DEBUG=1 # default: 0 (pass debug env var through) -# CODEX_LINUX_SANDBOX_USE_BWRAP=1 # default: 1 (run the bwrap suite) -# CODEX_LINUX_SANDBOX_USE_LEGACY=1 # default: 1 (run the legacy suite) -# CODEX_LINUX_SANDBOX_USE_VENDORED=1 # default: 0 (use build-time bwrap FFI) -# CODEX_LINUX_SANDBOX_BWRAP_PATH # default: $(command -v bwrap) - -if [[ "$(uname -s)" != "Linux" ]]; then - echo "This script is intended to run on Linux." >&2 - exit 1 -fi - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" -CODEX_RS_DIR="${REPO_ROOT}/codex-rs" - -NO_PROC="${CODEX_LINUX_SANDBOX_NO_PROC:-1}" -DEBUG="${CODEX_LINUX_SANDBOX_DEBUG:-0}" -USE_BWRAP_SUITE="${CODEX_LINUX_SANDBOX_USE_BWRAP:-1}" -USE_LEGACY_SUITE="${CODEX_LINUX_SANDBOX_USE_LEGACY:-1}" -USE_VENDORED="${CODEX_LINUX_SANDBOX_USE_VENDORED:-0}" - -BWRAP_PATH="" -if [[ "${USE_VENDORED}" != "1" ]]; then - BWRAP_PATH="${CODEX_LINUX_SANDBOX_BWRAP_PATH:-$(command -v bwrap || true)}" - if [[ -z "${BWRAP_PATH}" ]]; then - echo "bubblewrap (bwrap) is required but was not found on PATH." >&2 - exit 1 - fi -fi - -SANDBOX_BIN="${CODEX_RS_DIR}/target/debug/codex-linux-sandbox" -tmp_root="" - -build_binary() { - echo "==> Building codex-linux-sandbox" - (cd "${CODEX_RS_DIR}" && cargo build -p codex-linux-sandbox >/dev/null) -} - -policy_json() { - local network_access="$1" - printf '{"type":"workspace-write","writable_roots":[],"network_access":%s}' "${network_access}" -} - -run_sandbox() { - local network_access="$1" - local use_bwrap="$2" - shift - shift - - local no_proc_flag=() - if [[ "${NO_PROC}" == "1" && "${use_bwrap}" == "1" ]]; then - no_proc_flag=(--no-proc) - fi - - local debug_env=() - if [[ "${DEBUG}" == "1" ]]; then - debug_env=(env CODEX_LINUX_SANDBOX_DEBUG=1) - fi - - local bwrap_flag=() - if [[ "${use_bwrap}" == "1" ]]; then - if [[ "${USE_VENDORED}" == "1" ]]; then - bwrap_flag=(--use-vendored-bwrap) - else - bwrap_flag=(--bwrap-path "${BWRAP_PATH}") - fi - fi - - "${debug_env[@]}" "${SANDBOX_BIN}" \ - --sandbox-policy-cwd "${REPO_ROOT}" \ - --sandbox-policy "$(policy_json "${network_access}")" \ - "${bwrap_flag[@]}" \ - "${no_proc_flag[@]}" \ - -- "$@" -} - -expect_success() { - local label="$1" - local network_access="$2" - local use_bwrap="$3" - shift - shift - shift - echo "==> ${label}" - if run_sandbox "${network_access}" "${use_bwrap}" "$@"; then - echo " PASS" - else - echo " FAIL (expected success)" >&2 - exit 1 - fi -} - -expect_failure() { - local label="$1" - local network_access="$2" - local use_bwrap="$3" - shift - shift - shift - echo "==> ${label}" - if run_sandbox "${network_access}" "${use_bwrap}" "$@"; then - echo " FAIL (expected failure)" >&2 - exit 1 - else - echo " PASS (failed as expected)" - fi -} - -run_suite() { - local suite_name="$1" - local use_bwrap="$2" - - echo - echo "==== Suite: ${suite_name} (use_bwrap=${use_bwrap}) ====" - - # Create a disposable writable root for workspace-write checks. - if [[ -n "${tmp_root:-}" ]]; then - rm -rf -- "${tmp_root}" - fi - tmp_root="$(mktemp -d "${REPO_ROOT}/.codex-sandbox-test.XXXXXX")" - trap 'rm -rf -- "${tmp_root:-}"' EXIT - - mkdir -p "${REPO_ROOT}/.codex" - - expect_success \ - "workspace write succeeds inside repo" \ - true \ - "${use_bwrap}" \ - /usr/bin/bash -lc "cd '${tmp_root}' && touch OK_IN_WORKSPACE" - - expect_failure \ - "writes outside allowed roots fail" \ - true \ - "${use_bwrap}" \ - /usr/bin/bash -lc "touch /etc/SHOULD_FAIL" - - # Only the bwrap suite enforces `.git` and `.codex` as read-only. - if [[ "${use_bwrap}" == "1" ]]; then - expect_failure \ - ".git and .codex remain read-only (bwrap)" \ - true \ - "${use_bwrap}" \ - /usr/bin/bash -lc "cd '${REPO_ROOT}' && touch .git/SHOULD_FAIL && touch .codex/SHOULD_FAIL" - else - expect_success \ - ".git and .codex are NOT protected in legacy landlock path" \ - true \ - "${use_bwrap}" \ - /usr/bin/bash -lc "cd '${REPO_ROOT}' && mkdir -p .codex && touch .git/SHOULD_SUCCEED && touch .codex/SHOULD_SUCCEED" - fi - - expect_failure \ - "network_access=false blocks outbound sockets" \ - false \ - "${use_bwrap}" \ - /usr/bin/bash -lc "exec 3<>/dev/tcp/1.1.1.1/443" -} - -main() { - build_binary - - if [[ "${USE_BWRAP_SUITE}" == "1" ]]; then - run_suite "bwrap opt-in" "1" - fi - - if [[ "${USE_LEGACY_SUITE}" == "1" ]]; then - run_suite "legacy default" "0" - fi - - echo - echo "All requested linux-sandbox suites passed." -} - -main "$@" From 077419b200cc2c78ad525c9a9036d464d03645eb Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 2 Feb 2026 19:34:49 -0800 Subject: [PATCH 6/7] chore(linux-sandbox): bump vendoring branch --- codex-rs/Cargo.lock | 2 ++ codex-rs/linux-sandbox/Cargo.toml | 2 ++ codex-rs/linux-sandbox/src/linux_run_main.rs | 9 +++------ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index abf00bdbacc..c1e5c0659cc 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1666,8 +1666,10 @@ dependencies = [ "pkg-config", "pretty_assertions", "seccompiler", + "serde_json", "tempfile", "tokio", + "which", ] [[package]] diff --git a/codex-rs/linux-sandbox/Cargo.toml b/codex-rs/linux-sandbox/Cargo.toml index 503c22e6fe2..3feb1eb7b86 100644 --- a/codex-rs/linux-sandbox/Cargo.toml +++ b/codex-rs/linux-sandbox/Cargo.toml @@ -22,6 +22,8 @@ codex-utils-absolute-path = { workspace = true } landlock = { workspace = true } libc = { workspace = true } seccompiler = { workspace = true } +serde_json = { workspace = true } +which = "8.0.0" [target.'cfg(target_os = "linux")'.dev-dependencies] pretty_assertions = { workspace = true } diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index 346ab7735dd..3f6cd3ac86e 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -87,8 +87,7 @@ pub fn run_main() -> ! { // Inner stage: apply seccomp/no_new_privs after bubblewrap has already // established the filesystem view. if apply_seccomp_then_exec { - if let Err(e) = - apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) + if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) { panic!("error applying Linux sandbox restrictions: {e:?}"); } @@ -96,8 +95,7 @@ pub fn run_main() -> ! { } let command = if sandbox_policy.has_full_disk_write_access() { - if let Err(e) = - apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) + if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) { panic!("error applying Linux sandbox restrictions: {e:?}"); } @@ -149,8 +147,7 @@ pub fn run_main() -> ! { .unwrap_or_else(|err| panic!("error building bubblewrap command: {err:?}")) } else { // Legacy path: Landlock enforcement only. - if let Err(e) = - apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, true) + if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) { panic!("error applying legacy Linux sandbox restrictions: {e:?}"); } From 142d041798d7ee5f74a47136ccbd8a8303dbac71 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 2 Feb 2026 22:34:23 -0800 Subject: [PATCH 7/7] style(linux-sandbox): use raw string for generated config header --- codex-rs/linux-sandbox/build.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codex-rs/linux-sandbox/build.rs b/codex-rs/linux-sandbox/build.rs index 492ae3af7b9..6b73e0e21f2 100644 --- a/codex-rs/linux-sandbox/build.rs +++ b/codex-rs/linux-sandbox/build.rs @@ -58,7 +58,9 @@ fn try_build_vendored_bwrap() -> Result<(), String> { let config_h = out_dir.join("config.h"); std::fs::write( &config_h, - "#pragma once\n#define PACKAGE_STRING \"bubblewrap built at codex build-time\"\n", + r#"#pragma once +#define PACKAGE_STRING "bubblewrap built at codex build-time" +"#, ) .map_err(|err| format!("failed to write {}: {err}", config_h.display()))?;