mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
29 Commits
shareable-
...
viyat/bwra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
666c35091a | ||
|
|
8273e9e8aa | ||
|
|
3cb7678e0a | ||
|
|
05b025f311 | ||
|
|
f32fc95376 | ||
|
|
c960b0881f | ||
|
|
ade723257f | ||
|
|
2f38647a2e | ||
|
|
fbcd5ad9a5 | ||
|
|
5063676691 | ||
|
|
ea3f414631 | ||
|
|
50c3b38723 | ||
|
|
683f7cfe74 | ||
|
|
905c4fb6b1 | ||
|
|
811a8bef38 | ||
|
|
e900c6c61d | ||
|
|
f3a860782e | ||
|
|
cb6a885cda | ||
|
|
982693e0fe | ||
|
|
cf62897926 | ||
|
|
f3beeaefa9 | ||
|
|
4387f44060 | ||
|
|
425ae2cd52 | ||
|
|
9be8e39ac2 | ||
|
|
6e8e4bdbaa | ||
|
|
81d9915cc0 | ||
|
|
4c6ad6c386 | ||
|
|
7d9a7560fe | ||
|
|
c5f7930a01 |
4
codex-rs/Cargo.lock
generated
4
codex-rs/Cargo.lock
generated
@@ -1593,15 +1593,19 @@ 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",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1294,6 +1294,7 @@ impl CodexMessageProcessor {
|
||||
let outgoing = self.outgoing.clone();
|
||||
let req_id = request_id;
|
||||
let sandbox_cwd = self.config.cwd.clone();
|
||||
let use_linux_sandbox_bwrap = self.config.features.enabled(Feature::UseLinuxSandboxBwrap);
|
||||
|
||||
tokio::spawn(async move {
|
||||
match codex_core::exec::process_exec_tool_call(
|
||||
@@ -1301,6 +1302,7 @@ impl CodexMessageProcessor {
|
||||
&effective_policy,
|
||||
sandbox_cwd.as_path(),
|
||||
&codex_linux_sandbox_exe,
|
||||
use_linux_sandbox_bwrap,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -224,16 +224,19 @@ async fn run_command_under_sandbox(
|
||||
.await?
|
||||
}
|
||||
SandboxType::Landlock => {
|
||||
use codex_core::features::Feature;
|
||||
#[expect(clippy::expect_used)]
|
||||
let codex_linux_sandbox_exe = config
|
||||
.codex_linux_sandbox_exe
|
||||
.expect("codex-linux-sandbox executable not found");
|
||||
let use_bwrap_sandbox = config.features.enabled(Feature::UseLinuxSandboxBwrap);
|
||||
spawn_command_under_linux_sandbox(
|
||||
codex_linux_sandbox_exe,
|
||||
command,
|
||||
cwd,
|
||||
config.sandbox_policy.get(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
use_bwrap_sandbox,
|
||||
stdio_policy,
|
||||
env,
|
||||
)
|
||||
|
||||
@@ -207,6 +207,9 @@
|
||||
"unified_exec": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"use_linux_sandbox_bwrap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"web_search": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -1200,6 +1203,9 @@
|
||||
"unified_exec": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"use_linux_sandbox_bwrap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"web_search": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -451,6 +451,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
||||
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
|
||||
pub(crate) tools_config: ToolsConfig,
|
||||
pub(crate) features: Features,
|
||||
pub(crate) ghost_snapshot: GhostSnapshotConfig,
|
||||
pub(crate) final_output_json_schema: Option<Value>,
|
||||
pub(crate) codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
@@ -632,6 +633,7 @@ impl Session {
|
||||
windows_sandbox_level: session_configuration.windows_sandbox_level,
|
||||
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
features: per_turn_config.features.clone(),
|
||||
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
|
||||
final_output_json_schema: None,
|
||||
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
|
||||
@@ -877,6 +879,7 @@ impl Session {
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: session_configuration.cwd.clone(),
|
||||
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
};
|
||||
let cancel_token = sess.mcp_startup_cancellation_token().await;
|
||||
|
||||
@@ -1117,6 +1120,9 @@ impl Session {
|
||||
sandbox_policy: per_turn_config.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: per_turn_config.cwd.clone(),
|
||||
use_linux_sandbox_bwrap: per_turn_config
|
||||
.features
|
||||
.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
};
|
||||
if let Err(e) = self
|
||||
.services
|
||||
@@ -2138,6 +2144,7 @@ impl Session {
|
||||
sandbox_policy: turn_context.sandbox_policy.clone(),
|
||||
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: turn_context.cwd.clone(),
|
||||
use_linux_sandbox_bwrap: turn_context.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
};
|
||||
let cancel_token = self.reset_mcp_startup_cancellation_token().await;
|
||||
|
||||
@@ -2924,6 +2931,7 @@ async fn spawn_review_thread(
|
||||
sub_id: sub_id.to_string(),
|
||||
client,
|
||||
tools_config,
|
||||
features: parent_turn_context.features.clone(),
|
||||
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
|
||||
developer_instructions: None,
|
||||
user_instructions: None,
|
||||
|
||||
@@ -59,6 +59,7 @@ pub async fn list_accessible_connectors_from_mcp_tools(
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
|
||||
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
};
|
||||
|
||||
mcp_connection_manager
|
||||
|
||||
@@ -140,6 +140,7 @@ pub async fn process_exec_tool_call(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
sandbox_cwd: &Path,
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_linux_sandbox_bwrap: bool,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
let windows_sandbox_level = params.windows_sandbox_level;
|
||||
@@ -184,14 +185,15 @@ pub async fn process_exec_tool_call(
|
||||
|
||||
let manager = SandboxManager::new();
|
||||
let exec_env = manager
|
||||
.transform(
|
||||
.transform(crate::sandboxing::SandboxTransformRequest {
|
||||
spec,
|
||||
sandbox_policy,
|
||||
sandbox_type,
|
||||
sandbox_cwd,
|
||||
codex_linux_sandbox_exe.as_ref(),
|
||||
policy: sandbox_policy,
|
||||
sandbox: sandbox_type,
|
||||
sandbox_policy_cwd: sandbox_cwd,
|
||||
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(),
|
||||
use_linux_sandbox_bwrap,
|
||||
windows_sandbox_level,
|
||||
)
|
||||
})
|
||||
.map_err(CodexErr::from)?;
|
||||
|
||||
// Route through the sandboxing module for a single, unified execution path.
|
||||
@@ -1035,6 +1037,7 @@ mod tests {
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
cwd.as_path(),
|
||||
&None,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -89,6 +89,8 @@ pub enum Feature {
|
||||
WebSearchCached,
|
||||
/// Gate the execpolicy enforcement for shell/unified exec.
|
||||
ExecPolicy,
|
||||
/// Use the bubblewrap-based Linux sandbox pipeline.
|
||||
UseLinuxSandboxBwrap,
|
||||
/// Enable Windows sandbox (restricted token) on Windows.
|
||||
WindowsSandbox,
|
||||
/// Use the elevated Windows sandbox pipeline (setup + runner).
|
||||
@@ -391,6 +393,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UseLinuxSandboxBwrap,
|
||||
key: "use_linux_sandbox_bwrap",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::WindowsSandbox,
|
||||
key: "experimental_windows_sandbox",
|
||||
|
||||
@@ -6,26 +6,34 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tokio::process::Child;
|
||||
|
||||
/// Spawn a shell tool command under the Linux Landlock+seccomp sandbox helper
|
||||
/// (codex-linux-sandbox).
|
||||
/// Spawn a shell tool command under the Linux sandbox helper
|
||||
/// (codex-linux-sandbox), which currently uses bubblewrap for filesystem
|
||||
/// isolation plus seccomp for network restrictions.
|
||||
///
|
||||
/// Unlike macOS Seatbelt where we directly embed the policy text, the Linux
|
||||
/// helper accepts a list of `--sandbox-permission`/`-s` flags mirroring the
|
||||
/// public CLI. We convert the internal [`SandboxPolicy`] representation into
|
||||
/// the equivalent CLI options.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn spawn_command_under_linux_sandbox<P>(
|
||||
codex_linux_sandbox_exe: P,
|
||||
command: Vec<String>,
|
||||
command_cwd: PathBuf,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
use_bwrap_sandbox: bool,
|
||||
stdio_policy: StdioPolicy,
|
||||
env: HashMap<String, String>,
|
||||
) -> std::io::Result<Child>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let args = create_linux_sandbox_command_args(command, sandbox_policy, sandbox_policy_cwd);
|
||||
let args = create_linux_sandbox_command_args(
|
||||
command,
|
||||
sandbox_policy,
|
||||
sandbox_policy_cwd,
|
||||
use_bwrap_sandbox,
|
||||
);
|
||||
let arg0 = Some("codex-linux-sandbox");
|
||||
spawn_child_async(
|
||||
codex_linux_sandbox_exe.as_ref().to_path_buf(),
|
||||
@@ -40,10 +48,14 @@ where
|
||||
}
|
||||
|
||||
/// Converts the sandbox policy into the CLI invocation for `codex-linux-sandbox`.
|
||||
///
|
||||
/// The helper performs the actual sandboxing (bubblewrap + seccomp) after
|
||||
/// parsing these arguments. See `docs/linux_sandbox.md` for the Linux semantics.
|
||||
pub(crate) fn create_linux_sandbox_command_args(
|
||||
command: Vec<String>,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
use_bwrap_sandbox: bool,
|
||||
) -> Vec<String> {
|
||||
#[expect(clippy::expect_used)]
|
||||
let sandbox_policy_cwd = sandbox_policy_cwd
|
||||
@@ -60,13 +72,51 @@ pub(crate) fn create_linux_sandbox_command_args(
|
||||
sandbox_policy_cwd,
|
||||
"--sandbox-policy".to_string(),
|
||||
sandbox_policy_json,
|
||||
// Separator so that command arguments starting with `-` are not parsed as
|
||||
// options of the helper itself.
|
||||
"--".to_string(),
|
||||
];
|
||||
if use_bwrap_sandbox {
|
||||
linux_cmd.push("--use-bwrap-sandbox".to_string());
|
||||
linux_cmd.push("--use-vendored-bwrap".to_string());
|
||||
}
|
||||
|
||||
// Separator so that command arguments starting with `-` are not parsed as
|
||||
// options of the helper itself.
|
||||
linux_cmd.push("--".to_string());
|
||||
|
||||
// Append the original tool command.
|
||||
linux_cmd.extend(command);
|
||||
|
||||
linux_cmd
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn bwrap_flags_are_feature_gated() {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let cwd = Path::new("/tmp");
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
|
||||
let with_bwrap = create_linux_sandbox_command_args(command.clone(), &policy, cwd, true);
|
||||
assert_eq!(
|
||||
with_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
with_bwrap.contains(&"--use-vendored-bwrap".to_string()),
|
||||
true
|
||||
);
|
||||
|
||||
let without_bwrap = create_linux_sandbox_command_args(command, &policy, cwd, false);
|
||||
assert_eq!(
|
||||
without_bwrap.contains(&"--use-bwrap-sandbox".to_string()),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
without_bwrap.contains(&"--use-vendored-bwrap".to_string()),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
|
||||
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
};
|
||||
|
||||
mcp_connection_manager
|
||||
|
||||
@@ -302,6 +302,8 @@ pub struct SandboxState {
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
pub sandbox_cwd: PathBuf,
|
||||
#[serde(default)]
|
||||
pub use_linux_sandbox_bwrap: bool,
|
||||
}
|
||||
|
||||
/// A thin wrapper around a set of running [`RmcpClient`] instances.
|
||||
|
||||
@@ -51,6 +51,19 @@ pub struct ExecEnv {
|
||||
pub arg0: Option<String>,
|
||||
}
|
||||
|
||||
/// Bundled arguments for sandbox transformation.
|
||||
///
|
||||
/// This keeps call sites self-documenting when several fields are optional.
|
||||
pub(crate) struct SandboxTransformRequest<'a> {
|
||||
pub spec: CommandSpec,
|
||||
pub policy: &'a SandboxPolicy,
|
||||
pub sandbox: SandboxType,
|
||||
pub sandbox_policy_cwd: &'a Path,
|
||||
pub codex_linux_sandbox_exe: Option<&'a PathBuf>,
|
||||
pub use_linux_sandbox_bwrap: bool,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
}
|
||||
|
||||
pub enum SandboxPreference {
|
||||
Auto,
|
||||
Require,
|
||||
@@ -104,13 +117,17 @@ impl SandboxManager {
|
||||
|
||||
pub(crate) fn transform(
|
||||
&self,
|
||||
mut spec: CommandSpec,
|
||||
policy: &SandboxPolicy,
|
||||
sandbox: SandboxType,
|
||||
sandbox_policy_cwd: &Path,
|
||||
codex_linux_sandbox_exe: Option<&PathBuf>,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
request: SandboxTransformRequest<'_>,
|
||||
) -> Result<ExecEnv, SandboxTransformError> {
|
||||
let SandboxTransformRequest {
|
||||
mut spec,
|
||||
policy,
|
||||
sandbox,
|
||||
sandbox_policy_cwd,
|
||||
codex_linux_sandbox_exe,
|
||||
use_linux_sandbox_bwrap,
|
||||
windows_sandbox_level,
|
||||
} = request;
|
||||
let mut env = spec.env;
|
||||
if !policy.has_full_network_access() {
|
||||
env.insert(
|
||||
@@ -141,8 +158,12 @@ impl SandboxManager {
|
||||
SandboxType::LinuxSeccomp => {
|
||||
let exe = codex_linux_sandbox_exe
|
||||
.ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
|
||||
let mut args =
|
||||
create_linux_sandbox_command_args(command.clone(), policy, sandbox_policy_cwd);
|
||||
let mut args = create_linux_sandbox_command_args(
|
||||
command.clone(),
|
||||
policy,
|
||||
sandbox_policy_cwd,
|
||||
use_linux_sandbox_bwrap,
|
||||
);
|
||||
let mut full_command = Vec::with_capacity(1 + args.len());
|
||||
full_command.push(exe.to_string_lossy().to_string());
|
||||
full_command.append(&mut args);
|
||||
|
||||
@@ -8,6 +8,7 @@ retry without sandbox on denial (no re‑approval thanks to caching).
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::SandboxErr;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
use crate::features::Feature;
|
||||
use crate::sandboxing::SandboxManager;
|
||||
use crate::tools::sandboxing::ApprovalCtx;
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
@@ -97,12 +98,14 @@ impl ToolOrchestrator {
|
||||
|
||||
// Platform-specific flag gating is handled by SandboxManager::select_initial
|
||||
// via crate::safety::get_platform_sandbox(..).
|
||||
let use_linux_sandbox_bwrap = turn_ctx.features.enabled(Feature::UseLinuxSandboxBwrap);
|
||||
let initial_attempt = SandboxAttempt {
|
||||
sandbox: initial_sandbox,
|
||||
policy: &turn_ctx.sandbox_policy,
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd: &turn_ctx.cwd,
|
||||
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
|
||||
use_linux_sandbox_bwrap,
|
||||
windows_sandbox_level: turn_ctx.windows_sandbox_level,
|
||||
};
|
||||
|
||||
@@ -154,6 +157,7 @@ impl ToolOrchestrator {
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd: &turn_ctx.cwd,
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_linux_sandbox_bwrap,
|
||||
windows_sandbox_level: turn_ctx.windows_sandbox_level,
|
||||
};
|
||||
|
||||
|
||||
@@ -274,6 +274,7 @@ pub(crate) struct SandboxAttempt<'a> {
|
||||
pub(crate) manager: &'a SandboxManager,
|
||||
pub(crate) sandbox_cwd: &'a Path,
|
||||
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
|
||||
pub use_linux_sandbox_bwrap: bool,
|
||||
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
|
||||
}
|
||||
|
||||
@@ -282,14 +283,16 @@ impl<'a> SandboxAttempt<'a> {
|
||||
&self,
|
||||
spec: CommandSpec,
|
||||
) -> Result<crate::sandboxing::ExecEnv, SandboxTransformError> {
|
||||
self.manager.transform(
|
||||
spec,
|
||||
self.policy,
|
||||
self.sandbox,
|
||||
self.sandbox_cwd,
|
||||
self.codex_linux_sandbox_exe,
|
||||
self.windows_sandbox_level,
|
||||
)
|
||||
self.manager
|
||||
.transform(crate::sandboxing::SandboxTransformRequest {
|
||||
spec,
|
||||
policy: self.policy,
|
||||
sandbox: self.sandbox,
|
||||
sandbox_policy_cwd: self.sandbox_cwd,
|
||||
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe,
|
||||
use_linux_sandbox_bwrap: self.use_linux_sandbox_bwrap,
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
|
||||
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
process_exec_tool_call(params, &policy, tmp.path(), &None, None).await
|
||||
process_exec_tool_call(params, &policy, tmp.path(), &None, false, None).await
|
||||
}
|
||||
|
||||
/// Command succeeds with exit code 0 normally
|
||||
|
||||
59
codex-rs/docs/linux_sandbox.md
Normal file
59
codex-rs/docs/linux_sandbox.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Linux Sandbox (Vendored bubblewrap, Landlock legacy)
|
||||
|
||||
The Linux sandbox helper (`codex-linux-sandbox`) supports two filesystem
|
||||
pipelines:
|
||||
- Vendored bubblewrap (feature-flag opt-in): bubblewrap (`bwrap`) for
|
||||
filesystem isolation plus seccomp.
|
||||
- Legacy: mount-based protections plus Landlock enforcement.
|
||||
|
||||
## Requirements
|
||||
|
||||
- The bubblewrap pipeline requires a Linux build that enables the vendored
|
||||
bubblewrap FFI path.
|
||||
- During rollout, enable bubblewrap with
|
||||
`features.use_linux_sandbox_bwrap = true`.
|
||||
|
||||
## Filesystem Semantics (bubblewrap pipeline)
|
||||
|
||||
When the bubblewrap pipeline is enabled and disk writes are restricted
|
||||
(`read-only` or `workspace-write`), the helper builds the filesystem view with
|
||||
bubblewrap in this order:
|
||||
|
||||
1. `--ro-bind / /` makes the entire filesystem read-only.
|
||||
2. `--bind <root> <root>` re-enables writes for each writable root.
|
||||
3. `--ro-bind <subpath> <subpath>` re-applies read-only protections under
|
||||
writable roots so protected paths win.
|
||||
4. `--dev-bind /dev/null /dev/null` preserves the common sink.
|
||||
|
||||
Writable roots and protected subpaths are derived from
|
||||
`SandboxPolicy::get_writable_roots_with_cwd()`.
|
||||
|
||||
Protected subpaths include:
|
||||
- top-level `.git` (directory or pointer file),
|
||||
- the resolved `gitdir:` target for worktrees and submodules, and
|
||||
- top-level `.codex`.
|
||||
|
||||
### Deny-path Hardening
|
||||
|
||||
To reduce symlink and path-creation attacks inside writable roots:
|
||||
- If any component of a protected path is a symlink within a writable root, the
|
||||
helper mounts `/dev/null` on that symlink.
|
||||
- If a protected path does not exist, the helper mounts `/dev/null` on the
|
||||
first missing path component (when it is within a writable root).
|
||||
|
||||
## Process and Network Semantics
|
||||
|
||||
- In the bubblewrap pipeline, the helper isolates the PID namespace via
|
||||
`--unshare-pid`.
|
||||
- In the bubblewrap pipeline, it mounts a fresh `/proc` via `--proc /proc` by
|
||||
default.
|
||||
- In restrictive container environments, you can skip the `/proc` mount with
|
||||
the helper flag `--no-proc` while still keeping PID isolation enabled.
|
||||
- Network restrictions are enforced with seccomp when network access is
|
||||
disabled.
|
||||
|
||||
## Notes
|
||||
|
||||
- The CLI still exposes legacy names such as `codex debug landlock`.
|
||||
- Landlock remains the default filesystem enforcement pipeline when
|
||||
`features.use_linux_sandbox_bwrap` is not enabled.
|
||||
@@ -95,6 +95,7 @@ impl EscalateServer {
|
||||
&sandbox_state.sandbox_policy,
|
||||
&sandbox_state.sandbox_cwd,
|
||||
&sandbox_state.codex_linux_sandbox_exe,
|
||||
sandbox_state.use_linux_sandbox_bwrap,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -126,6 +126,7 @@ impl ExecTool {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
codex_linux_sandbox_exe: None,
|
||||
sandbox_cwd: PathBuf::from(¶ms.workdir),
|
||||
use_linux_sandbox_bwrap: false,
|
||||
});
|
||||
let escalate_server = EscalateServer::new(
|
||||
self.bash_path.clone(),
|
||||
|
||||
@@ -91,6 +91,7 @@ where
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
codex_linux_sandbox_exe,
|
||||
sandbox_cwd: sandbox_cwd.as_ref().to_path_buf(),
|
||||
use_linux_sandbox_bwrap: false,
|
||||
};
|
||||
send_sandbox_state_update(sandbox_state, service).await
|
||||
}
|
||||
@@ -118,6 +119,7 @@ where
|
||||
},
|
||||
codex_linux_sandbox_exe,
|
||||
sandbox_cwd: writable_folder.as_ref().to_path_buf(),
|
||||
use_linux_sandbox_bwrap: false,
|
||||
};
|
||||
send_sandbox_state_update(sandbox_state, service).await
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ async fn spawn_command_under_sandbox(
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
false,
|
||||
stdio_policy,
|
||||
env,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,8 @@ codex-utils-absolute-path = { workspace = true }
|
||||
landlock = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
seccompiler = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
which = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
@@ -33,3 +35,7 @@ tokio = { workspace = true, features = [
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
pkg-config = "0.3"
|
||||
|
||||
@@ -6,3 +6,28 @@ This crate is responsible for producing:
|
||||
- a lib crate that exposes the business logic of the executable as `run_main()` so that
|
||||
- the `codex-exec` CLI can check if its arg0 is `codex-linux-sandbox` and, if so, execute as if it were `codex-linux-sandbox`
|
||||
- this should also be true of the `codex` multitool CLI
|
||||
|
||||
On Linux, the bubblewrap pipeline uses the vendored bubblewrap path compiled
|
||||
into this binary.
|
||||
|
||||
**Current Behavior**
|
||||
- Legacy Landlock + mount protections remain available as the legacy pipeline.
|
||||
- The bubblewrap pipeline is standardized on the vendored path.
|
||||
- During rollout, the bubblewrap pipeline is gated by the temporary feature
|
||||
flag `features.use_linux_sandbox_bwrap` (legacy remains default when off).
|
||||
- When enabled, the bubblewrap pipeline applies `PR_SET_NO_NEW_PRIVS` and a
|
||||
seccomp network filter in-process.
|
||||
- When enabled, the filesystem is read-only by default via `--ro-bind / /`.
|
||||
- When enabled, writable roots are layered with `--bind <root> <root>`.
|
||||
- When enabled, protected subpaths under writable roots (for example `.git`,
|
||||
resolved `gitdir:`, and `.codex`) are re-applied as read-only via `--ro-bind`.
|
||||
- When enabled, symlink-in-path and non-existent protected paths inside
|
||||
writable roots are blocked by mounting `/dev/null` on the symlink or first
|
||||
missing component.
|
||||
- When enabled, the helper isolates the PID namespace via `--unshare-pid`.
|
||||
- When enabled, it mounts a fresh `/proc` via `--proc /proc` by default, but
|
||||
you can skip this in restrictive container environments with `--no-proc`.
|
||||
|
||||
**Notes**
|
||||
- The CLI surface still uses legacy names like `codex debug landlock`.
|
||||
- See `docs/linux_sandbox.md` for the full Linux sandbox semantics.
|
||||
|
||||
113
codex-rs/linux-sandbox/build.rs
Normal file
113
codex-rs/linux-sandbox/build.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
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" {
|
||||
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 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(&manifest_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. The vendored bubblewrap tree under `codex-rs/vendor/bubblewrap`.
|
||||
fn resolve_bwrap_source_dir(manifest_dir: &Path) -> Result<PathBuf, String> {
|
||||
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 vendor_dir = manifest_dir.join("../vendor/bubblewrap");
|
||||
if vendor_dir.exists() {
|
||||
return Ok(vendor_dir);
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"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()
|
||||
))
|
||||
}
|
||||
222
codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh
Normal file
222
codex-rs/linux-sandbox/scripts/test_linux_sandbox.sh
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/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_BWRAP_ENABLE_FFI=1 # default: 1 (build vendored bwrap path)
|
||||
# CODEX_LINUX_SANDBOX_NO_PROC=0 # default: 0 (let helper auto-retry with --no-proc)
|
||||
# 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: 0 (run the legacy suite)
|
||||
# CODEX_LINUX_SANDBOX_USE_CODEX_CLI=1 # default: 1 (run codex CLI bwrap smoke)
|
||||
|
||||
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"
|
||||
|
||||
BWRAP_ENABLE_FFI="${CODEX_BWRAP_ENABLE_FFI:-1}"
|
||||
NO_PROC="${CODEX_LINUX_SANDBOX_NO_PROC:-0}"
|
||||
DEBUG="${CODEX_LINUX_SANDBOX_DEBUG:-0}"
|
||||
USE_BWRAP_SUITE="${CODEX_LINUX_SANDBOX_USE_BWRAP:-1}"
|
||||
USE_LEGACY_SUITE="${CODEX_LINUX_SANDBOX_USE_LEGACY:-0}"
|
||||
USE_CODEX_CLI_SMOKE="${CODEX_LINUX_SANDBOX_USE_CODEX_CLI:-1}"
|
||||
|
||||
SANDBOX_BIN="${CODEX_RS_DIR}/target/debug/codex-linux-sandbox"
|
||||
CODEX_BIN="${CODEX_RS_DIR}/target/debug/codex"
|
||||
tmp_root=""
|
||||
|
||||
build_binaries() {
|
||||
echo "==> Building codex-linux-sandbox"
|
||||
(
|
||||
cd "${CODEX_RS_DIR}" && \
|
||||
CODEX_BWRAP_ENABLE_FFI="${BWRAP_ENABLE_FFI}" cargo build -p codex-linux-sandbox >/dev/null
|
||||
)
|
||||
|
||||
if [[ "${USE_CODEX_CLI_SMOKE}" == "1" ]]; then
|
||||
echo "==> Building codex (local target/debug/codex)"
|
||||
(
|
||||
cd "${CODEX_RS_DIR}" && \
|
||||
CODEX_BWRAP_ENABLE_FFI="${BWRAP_ENABLE_FFI}" cargo build -p codex-cli >/dev/null
|
||||
)
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
bwrap_flag=(--use-bwrap-sandbox --use-vendored-bwrap)
|
||||
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"
|
||||
}
|
||||
|
||||
run_codex_cli_smoke() {
|
||||
if [[ "${USE_CODEX_CLI_SMOKE}" != "1" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==== codex CLI smoke (feature flag path) ===="
|
||||
echo "==> codex -c features.use_linux_sandbox_bwrap=true sandbox linux ..."
|
||||
|
||||
local output=""
|
||||
if ! output=$(
|
||||
"${CODEX_BIN}" \
|
||||
-c features.use_linux_sandbox_bwrap=true \
|
||||
sandbox linux --full-auto -- /usr/bin/bash -lc 'echo BWRAP_OK' 2>&1
|
||||
); then
|
||||
echo "${output}" >&2
|
||||
echo " FAIL (expected codex CLI bwrap smoke success)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${output}" != *"BWRAP_OK"* ]]; then
|
||||
echo "${output}" >&2
|
||||
echo " FAIL (missing BWRAP_OK output)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${output}"
|
||||
echo " PASS"
|
||||
}
|
||||
|
||||
main() {
|
||||
build_binaries
|
||||
run_codex_cli_smoke
|
||||
|
||||
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 "$@"
|
||||
252
codex-rs/linux-sandbox/src/bwrap.rs
Normal file
252
codex-rs/linux-sandbox/src/bwrap.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
//! 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<String>,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
cwd: &Path,
|
||||
options: BwrapOptions,
|
||||
) -> Result<Vec<String>> {
|
||||
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<String>,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
cwd: &Path,
|
||||
options: BwrapOptions,
|
||||
) -> Result<Vec<String>> {
|
||||
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 <root> <root>` re-enables writes for allowed roots.
|
||||
/// 3. `--ro-bind <subpath> <subpath>` 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<Vec<String>> {
|
||||
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<PathBuf> = 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<PathBuf> {
|
||||
let mut subpaths: BTreeSet<PathBuf> = 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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
//! In-process Linux sandbox primitives: `no_new_privs` and seccomp.
|
||||
//!
|
||||
//! Filesystem restrictions are enforced by bubblewrap in `linux_run_main`.
|
||||
//! Landlock helpers remain available here as legacy/backup utilities.
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -8,6 +12,7 @@ use codex_core::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
use landlock::ABI;
|
||||
#[allow(unused_imports)]
|
||||
use landlock::Access;
|
||||
use landlock::AccessFs;
|
||||
use landlock::CompatLevel;
|
||||
@@ -27,11 +32,24 @@ use seccompiler::apply_filter;
|
||||
|
||||
/// Apply sandbox policies inside this thread so only the child inherits
|
||||
/// them, not the entire CLI process.
|
||||
///
|
||||
/// This function is responsible for:
|
||||
/// - enabling `PR_SET_NO_NEW_PRIVS` when restrictions apply, and
|
||||
/// - installing the network seccomp filter when network access is disabled.
|
||||
///
|
||||
/// Filesystem restrictions are intentionally handled by bubblewrap.
|
||||
pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
cwd: &Path,
|
||||
apply_landlock_fs: bool,
|
||||
) -> Result<()> {
|
||||
if !sandbox_policy.has_full_disk_write_access() || !sandbox_policy.has_full_network_access() {
|
||||
// `PR_SET_NO_NEW_PRIVS` is required for seccomp, but it also prevents
|
||||
// setuid privilege elevation. Many `bwrap` deployments rely on setuid, so
|
||||
// we avoid this unless we need seccomp or we are explicitly using the
|
||||
// legacy Landlock filesystem pipeline.
|
||||
if !sandbox_policy.has_full_network_access()
|
||||
|| (apply_landlock_fs && !sandbox_policy.has_full_disk_write_access())
|
||||
{
|
||||
set_no_new_privs()?;
|
||||
}
|
||||
|
||||
@@ -39,7 +57,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
install_network_seccomp_filter_on_current_thread()?;
|
||||
}
|
||||
|
||||
if !sandbox_policy.has_full_disk_write_access() {
|
||||
if apply_landlock_fs && !sandbox_policy.has_full_disk_write_access() {
|
||||
let writable_roots = sandbox_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.into_iter()
|
||||
@@ -54,6 +72,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable `PR_SET_NO_NEW_PRIVS` so seccomp can be applied safely.
|
||||
fn set_no_new_privs() -> Result<()> {
|
||||
let result = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };
|
||||
if result != 0 {
|
||||
@@ -68,6 +87,9 @@ fn set_no_new_privs() -> Result<()> {
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns [`CodexErr::Sandbox`] variants when the ruleset fails to apply.
|
||||
///
|
||||
/// Note: this is currently unused because filesystem sandboxing is performed
|
||||
/// via bubblewrap. It is kept for reference and potential fallback use.
|
||||
fn install_filesystem_landlock_rules_on_current_thread(
|
||||
writable_roots: Vec<AbsolutePathBuf>,
|
||||
) -> Result<()> {
|
||||
@@ -98,6 +120,9 @@ fn install_filesystem_landlock_rules_on_current_thread(
|
||||
|
||||
/// Installs a seccomp filter that blocks outbound network access except for
|
||||
/// AF_UNIX domain sockets.
|
||||
///
|
||||
/// The filter is applied to the current thread so only the sandboxed child
|
||||
/// inherits it.
|
||||
fn install_network_seccomp_filter_on_current_thread() -> std::result::Result<(), SandboxErr> {
|
||||
// Build rule map.
|
||||
let mut rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
|
||||
|
||||
@@ -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() -> ! {
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
use clap::Parser;
|
||||
use std::ffi::CString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::bwrap::BwrapOptions;
|
||||
use crate::bwrap::create_bwrap_command_args;
|
||||
use crate::landlock::apply_sandbox_policy_to_current_thread;
|
||||
use crate::vendored_bwrap::exec_vendored_bwrap;
|
||||
use crate::vendored_bwrap::run_vendored_bwrap_main;
|
||||
|
||||
#[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 +26,301 @@ 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,
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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 || 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);
|
||||
}
|
||||
|
||||
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:?}");
|
||||
}
|
||||
exec_or_panic(command);
|
||||
}
|
||||
|
||||
if use_bwrap_sandbox {
|
||||
// Outer stage: bubblewrap first, then re-enter this binary in the
|
||||
// sandboxed environment to apply seccomp. This path never falls back
|
||||
// to legacy Landlock on failure.
|
||||
let inner = build_inner_seccomp_command(
|
||||
&sandbox_policy_cwd,
|
||||
&sandbox_policy,
|
||||
use_bwrap_sandbox,
|
||||
command,
|
||||
);
|
||||
run_bwrap_with_proc_fallback(&sandbox_policy_cwd, &sandbox_policy, inner, !no_proc);
|
||||
}
|
||||
|
||||
// Legacy path: Landlock enforcement only, when bwrap sandboxing is not enabled.
|
||||
if let Err(e) =
|
||||
apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, true)
|
||||
{
|
||||
panic!("error applying legacy Linux sandbox restrictions: {e:?}");
|
||||
}
|
||||
exec_or_panic(command);
|
||||
}
|
||||
|
||||
fn run_bwrap_with_proc_fallback(
|
||||
sandbox_policy_cwd: &Path,
|
||||
sandbox_policy: &codex_core::protocol::SandboxPolicy,
|
||||
inner: Vec<String>,
|
||||
mount_proc: bool,
|
||||
) -> ! {
|
||||
let mut mount_proc = mount_proc;
|
||||
|
||||
if mount_proc && !preflight_proc_mount_support(sandbox_policy_cwd, sandbox_policy) {
|
||||
eprintln!("codex-linux-sandbox: bwrap could not mount /proc; retrying with --no-proc");
|
||||
mount_proc = false;
|
||||
}
|
||||
|
||||
let options = BwrapOptions { mount_proc };
|
||||
let argv = build_bwrap_argv(inner, sandbox_policy, sandbox_policy_cwd, options);
|
||||
exec_vendored_bwrap(argv);
|
||||
}
|
||||
|
||||
fn build_bwrap_argv(
|
||||
inner: Vec<String>,
|
||||
sandbox_policy: &codex_core::protocol::SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
options: BwrapOptions,
|
||||
) -> Vec<String> {
|
||||
let mut args = create_bwrap_command_args(inner, sandbox_policy, sandbox_policy_cwd, options)
|
||||
.unwrap_or_else(|err| panic!("error building bubblewrap command: {err:?}"));
|
||||
|
||||
let command_separator_index = args
|
||||
.iter()
|
||||
.position(|arg| arg == "--")
|
||||
.unwrap_or_else(|| panic!("bubblewrap argv is missing command separator '--'"));
|
||||
args.splice(
|
||||
command_separator_index..command_separator_index,
|
||||
["--argv0".to_string(), "codex-linux-sandbox".to_string()],
|
||||
);
|
||||
|
||||
let mut argv = vec!["bwrap".to_string()];
|
||||
argv.extend(args);
|
||||
argv
|
||||
}
|
||||
|
||||
fn preflight_proc_mount_support(
|
||||
sandbox_policy_cwd: &Path,
|
||||
sandbox_policy: &codex_core::protocol::SandboxPolicy,
|
||||
) -> bool {
|
||||
let preflight_command = vec![resolve_true_command()];
|
||||
let preflight_argv = build_bwrap_argv(
|
||||
preflight_command,
|
||||
sandbox_policy,
|
||||
sandbox_policy_cwd,
|
||||
BwrapOptions { mount_proc: true },
|
||||
);
|
||||
let stderr = run_bwrap_in_child_capture_stderr(preflight_argv);
|
||||
!is_proc_mount_failure(stderr.as_str())
|
||||
}
|
||||
|
||||
fn resolve_true_command() -> String {
|
||||
for candidate in ["/usr/bin/true", "/bin/true"] {
|
||||
if Path::new(candidate).exists() {
|
||||
return candidate.to_string();
|
||||
}
|
||||
}
|
||||
"true".to_string()
|
||||
}
|
||||
|
||||
fn run_bwrap_in_child_capture_stderr(argv: Vec<String>) -> String {
|
||||
let mut pipe_fds = [0; 2];
|
||||
let pipe_res = unsafe { libc::pipe2(pipe_fds.as_mut_ptr(), libc::O_CLOEXEC) };
|
||||
if pipe_res < 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("failed to create stderr pipe for bubblewrap: {err}");
|
||||
}
|
||||
let read_fd = pipe_fds[0];
|
||||
let write_fd = pipe_fds[1];
|
||||
|
||||
let pid = unsafe { libc::fork() };
|
||||
if pid < 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("failed to fork for bubblewrap: {err}");
|
||||
}
|
||||
|
||||
if pid == 0 {
|
||||
// Child: redirect stderr to the pipe, then run bubblewrap.
|
||||
unsafe {
|
||||
libc::close(read_fd);
|
||||
if libc::dup2(write_fd, libc::STDERR_FILENO) < 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("failed to redirect stderr for bubblewrap: {err}");
|
||||
}
|
||||
libc::close(write_fd);
|
||||
}
|
||||
|
||||
let exit_code = run_vendored_bwrap_main(&argv);
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
// Parent: close the write end and read stderr while the child runs.
|
||||
unsafe {
|
||||
libc::close(write_fd);
|
||||
}
|
||||
|
||||
let mut stderr = String::new();
|
||||
// SAFETY: `read_fd` is a valid owned fd in the parent.
|
||||
let mut read_file = unsafe { File::from_raw_fd(read_fd) };
|
||||
if let Err(err) = read_file.read_to_string(&mut stderr) {
|
||||
panic!("failed to read bubblewrap stderr: {err}");
|
||||
}
|
||||
|
||||
let mut status: libc::c_int = 0;
|
||||
let wait_res = unsafe { libc::waitpid(pid, &mut status as *mut libc::c_int, 0) };
|
||||
if wait_res < 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("waitpid failed for bubblewrap child: {err}");
|
||||
}
|
||||
|
||||
stderr
|
||||
}
|
||||
|
||||
fn is_proc_mount_failure(stderr: &str) -> bool {
|
||||
stderr.contains("Can't mount proc")
|
||||
&& stderr.contains("/newroot/proc")
|
||||
&& stderr.contains("Invalid argument")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn detects_proc_mount_invalid_argument_failure() {
|
||||
let stderr = "bwrap: Can't mount proc on /newroot/proc: Invalid argument";
|
||||
assert_eq!(is_proc_mount_failure(stderr), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_non_proc_mount_errors() {
|
||||
let stderr = "bwrap: Can't bind mount /dev/null: Operation not permitted";
|
||||
assert_eq!(is_proc_mount_failure(stderr), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserts_bwrap_argv0_before_command_separator() {
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&SandboxPolicy::ReadOnly,
|
||||
Path::new("/"),
|
||||
BwrapOptions { mount_proc: true },
|
||||
);
|
||||
let command_separator_index = argv
|
||||
.iter()
|
||||
.position(|arg| arg == "--")
|
||||
.expect("expected command separator in bubblewrap argv");
|
||||
let argv0_flag_index = argv
|
||||
.iter()
|
||||
.position(|arg| arg == "--argv0")
|
||||
.expect("expected --argv0 in bubblewrap argv");
|
||||
|
||||
assert_eq!(argv[0], "bwrap");
|
||||
assert_eq!(argv[argv0_flag_index + 1], "codex-linux-sandbox");
|
||||
assert_eq!(argv0_flag_index < command_separator_index, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
command: Vec<String>,
|
||||
) -> Vec<String> {
|
||||
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());
|
||||
}
|
||||
inner.push("--".to_string());
|
||||
inner.extend(command);
|
||||
inner
|
||||
}
|
||||
|
||||
/// Exec the provided argv, panicking with context if it fails.
|
||||
fn exec_or_panic(command: Vec<String>) -> ! {
|
||||
#[expect(clippy::expect_used)]
|
||||
let c_command =
|
||||
CString::new(command[0].as_str()).expect("Failed to convert command to CString");
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::error::Result;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::protocol::WritableRoot;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
/// Apply read-only bind mounts for protected subpaths before Landlock.
|
||||
///
|
||||
/// This unshares mount namespaces (and user namespaces for non-root) so the
|
||||
/// read-only remounts do not affect the host, then bind-mounts each protected
|
||||
/// target onto itself and remounts it read-only.
|
||||
pub(crate) fn apply_read_only_mounts(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Result<()> {
|
||||
let writable_roots = sandbox_policy.get_writable_roots_with_cwd(cwd);
|
||||
let mount_targets = collect_read_only_mount_targets(&writable_roots)?;
|
||||
if mount_targets.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Root can unshare the mount namespace directly; non-root needs a user
|
||||
// namespace to gain capabilities for remounting.
|
||||
if is_running_as_root() {
|
||||
unshare_mount_namespace()?;
|
||||
} else {
|
||||
let original_euid = unsafe { libc::geteuid() };
|
||||
let original_egid = unsafe { libc::getegid() };
|
||||
unshare_user_and_mount_namespaces()?;
|
||||
write_user_namespace_maps(original_euid, original_egid)?;
|
||||
}
|
||||
make_mounts_private()?;
|
||||
|
||||
for target in mount_targets {
|
||||
// Bind and remount read-only works for both files and directories.
|
||||
bind_mount_read_only(target.as_path())?;
|
||||
}
|
||||
|
||||
// Drop ambient capabilities acquired from the user namespace so the
|
||||
// sandboxed command cannot remount or create new bind mounts.
|
||||
if !is_running_as_root() {
|
||||
drop_caps()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Collect read-only mount targets, resolving worktree `.git` pointer files.
|
||||
fn collect_read_only_mount_targets(
|
||||
writable_roots: &[WritableRoot],
|
||||
) -> Result<Vec<AbsolutePathBuf>> {
|
||||
let mut targets = Vec::new();
|
||||
for writable_root in writable_roots {
|
||||
for ro_subpath in &writable_root.read_only_subpaths {
|
||||
// The policy expects these paths to exist; surface actionable errors
|
||||
// rather than silently skipping protections.
|
||||
if !ro_subpath.as_path().exists() {
|
||||
return Err(CodexErr::UnsupportedOperation(format!(
|
||||
"Sandbox expected to protect {path}, but it does not exist. Ensure the repository contains this path or create it before running Codex.",
|
||||
path = ro_subpath.as_path().display()
|
||||
)));
|
||||
}
|
||||
targets.push(ro_subpath.clone());
|
||||
// Worktrees and submodules store `.git` as a pointer file; add the
|
||||
// referenced gitdir as an extra read-only target.
|
||||
if is_git_pointer_file(ro_subpath) {
|
||||
let gitdir = resolve_gitdir_from_file(ro_subpath)?;
|
||||
if !targets
|
||||
.iter()
|
||||
.any(|target| target.as_path() == gitdir.as_path())
|
||||
{
|
||||
targets.push(gitdir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
/// Detect a `.git` pointer file used by worktrees and submodules.
|
||||
fn is_git_pointer_file(path: &AbsolutePathBuf) -> bool {
|
||||
path.as_path().is_file() && path.as_path().file_name() == Some(std::ffi::OsStr::new(".git"))
|
||||
}
|
||||
|
||||
/// Resolve a worktree `.git` pointer file to its gitdir path.
|
||||
fn resolve_gitdir_from_file(dot_git: &AbsolutePathBuf) -> Result<AbsolutePathBuf> {
|
||||
let contents = std::fs::read_to_string(dot_git.as_path()).map_err(CodexErr::from)?;
|
||||
let trimmed = contents.trim();
|
||||
let (_, gitdir_raw) = trimmed.split_once(':').ok_or_else(|| {
|
||||
CodexErr::UnsupportedOperation(format!(
|
||||
"Expected {path} to contain a gitdir pointer, but it did not match `gitdir: <path>`.",
|
||||
path = dot_git.as_path().display()
|
||||
))
|
||||
})?;
|
||||
// `gitdir: <path>` may be relative to the directory containing `.git`.
|
||||
let gitdir_raw = gitdir_raw.trim();
|
||||
if gitdir_raw.is_empty() {
|
||||
return Err(CodexErr::UnsupportedOperation(format!(
|
||||
"Expected {path} to contain a gitdir pointer, but it was empty.",
|
||||
path = dot_git.as_path().display()
|
||||
)));
|
||||
}
|
||||
let base = dot_git.as_path().parent().ok_or_else(|| {
|
||||
CodexErr::UnsupportedOperation(format!(
|
||||
"Unable to resolve parent directory for {path}.",
|
||||
path = dot_git.as_path().display()
|
||||
))
|
||||
})?;
|
||||
let gitdir_path = AbsolutePathBuf::resolve_path_against_base(gitdir_raw, base)?;
|
||||
if !gitdir_path.as_path().exists() {
|
||||
return Err(CodexErr::UnsupportedOperation(format!(
|
||||
"Resolved gitdir path {path} does not exist.",
|
||||
path = gitdir_path.as_path().display()
|
||||
)));
|
||||
}
|
||||
Ok(gitdir_path)
|
||||
}
|
||||
|
||||
/// Unshare the mount namespace so mount changes are isolated to the sandboxed process.
|
||||
fn unshare_mount_namespace() -> Result<()> {
|
||||
let result = unsafe { libc::unshare(libc::CLONE_NEWNS) };
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unshare user + mount namespaces so the process can remount read-only without privileges.
|
||||
fn unshare_user_and_mount_namespaces() -> Result<()> {
|
||||
let result = unsafe { libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNS) };
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_running_as_root() -> bool {
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CapUserHeader {
|
||||
version: u32,
|
||||
pid: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct CapUserData {
|
||||
effective: u32,
|
||||
permitted: u32,
|
||||
inheritable: u32,
|
||||
}
|
||||
|
||||
const LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522;
|
||||
|
||||
/// Map the provided uid/gid to root inside the user namespace.
|
||||
fn write_user_namespace_maps(uid: libc::uid_t, gid: libc::gid_t) -> Result<()> {
|
||||
write_proc_file("/proc/self/setgroups", "deny\n")?;
|
||||
|
||||
write_proc_file("/proc/self/uid_map", format!("0 {uid} 1\n"))?;
|
||||
write_proc_file("/proc/self/gid_map", format!("0 {gid} 1\n"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drop all capabilities in the current user namespace.
|
||||
fn drop_caps() -> Result<()> {
|
||||
let mut header = CapUserHeader {
|
||||
version: LINUX_CAPABILITY_VERSION_3,
|
||||
pid: 0,
|
||||
};
|
||||
let data = [
|
||||
CapUserData {
|
||||
effective: 0,
|
||||
permitted: 0,
|
||||
inheritable: 0,
|
||||
},
|
||||
CapUserData {
|
||||
effective: 0,
|
||||
permitted: 0,
|
||||
inheritable: 0,
|
||||
},
|
||||
];
|
||||
|
||||
// Use syscall directly to avoid libc capability symbols that are missing on musl.
|
||||
let result = unsafe { libc::syscall(libc::SYS_capset, &mut header, data.as_ptr()) };
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a small procfs file, returning a sandbox error on failure.
|
||||
fn write_proc_file(path: &str, contents: impl AsRef<[u8]>) -> Result<()> {
|
||||
std::fs::write(path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure mounts are private so remounting does not propagate outside the namespace.
|
||||
fn make_mounts_private() -> Result<()> {
|
||||
let root = CString::new("/").map_err(|_| {
|
||||
CodexErr::UnsupportedOperation("Sandbox mount path contains NUL byte: /".to_string())
|
||||
})?;
|
||||
let result = unsafe {
|
||||
libc::mount(
|
||||
std::ptr::null(),
|
||||
root.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_REC | libc::MS_PRIVATE,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bind-mount a path onto itself and remount read-only.
|
||||
fn bind_mount_read_only(path: &Path) -> Result<()> {
|
||||
let c_path = CString::new(path.as_os_str().as_bytes()).map_err(|_| {
|
||||
CodexErr::UnsupportedOperation(format!(
|
||||
"Sandbox mount path contains NUL byte: {path}",
|
||||
path = path.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
let bind_result = unsafe {
|
||||
libc::mount(
|
||||
c_path.as_ptr(),
|
||||
c_path.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_BIND,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if bind_result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
let remount_result = unsafe {
|
||||
libc::mount(
|
||||
c_path.as_ptr(),
|
||||
c_path.as_ptr(),
|
||||
std::ptr::null(),
|
||||
libc::MS_BIND | libc::MS_REMOUNT | libc::MS_RDONLY,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if remount_result != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn collect_read_only_mount_targets_errors_on_missing_path() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
||||
let missing = AbsolutePathBuf::try_from(tempdir.path().join("missing").as_path())
|
||||
.expect("missing path");
|
||||
let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root");
|
||||
let writable_root = WritableRoot {
|
||||
root,
|
||||
read_only_subpaths: vec![missing],
|
||||
};
|
||||
|
||||
let err = collect_read_only_mount_targets(&[writable_root])
|
||||
.expect_err("expected missing path error");
|
||||
let message = match err {
|
||||
CodexErr::UnsupportedOperation(message) => message,
|
||||
other => panic!("unexpected error: {other:?}"),
|
||||
};
|
||||
assert_eq!(
|
||||
message,
|
||||
format!(
|
||||
"Sandbox expected to protect {path}, but it does not exist. Ensure the repository contains this path or create it before running Codex.",
|
||||
path = tempdir.path().join("missing").display()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_read_only_mount_targets_adds_gitdir_for_pointer_file() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
||||
let gitdir = tempdir.path().join("actual-gitdir");
|
||||
std::fs::create_dir_all(&gitdir).expect("create gitdir");
|
||||
let dot_git = tempdir.path().join(".git");
|
||||
std::fs::write(&dot_git, format!("gitdir: {}\n", gitdir.display()))
|
||||
.expect("write gitdir pointer");
|
||||
let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root");
|
||||
let writable_root = WritableRoot {
|
||||
root,
|
||||
read_only_subpaths: vec![
|
||||
AbsolutePathBuf::try_from(dot_git.as_path()).expect("dot git"),
|
||||
],
|
||||
};
|
||||
|
||||
let targets = collect_read_only_mount_targets(&[writable_root]).expect("collect targets");
|
||||
assert_eq!(targets.len(), 2);
|
||||
assert_eq!(targets[0].as_path(), dot_git.as_path());
|
||||
assert_eq!(targets[1].as_path(), gitdir.as_path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_read_only_mount_targets_errors_on_invalid_gitdir_pointer() {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir");
|
||||
let dot_git = tempdir.path().join(".git");
|
||||
std::fs::write(&dot_git, "not-a-pointer\n").expect("write invalid pointer");
|
||||
let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root");
|
||||
let writable_root = WritableRoot {
|
||||
root,
|
||||
read_only_subpaths: vec![
|
||||
AbsolutePathBuf::try_from(dot_git.as_path()).expect("dot git"),
|
||||
],
|
||||
};
|
||||
|
||||
let err = collect_read_only_mount_targets(&[writable_root])
|
||||
.expect_err("expected invalid pointer error");
|
||||
let message = match err {
|
||||
CodexErr::UnsupportedOperation(message) => message,
|
||||
other => panic!("unexpected error: {other:?}"),
|
||||
};
|
||||
assert_eq!(
|
||||
message,
|
||||
format!(
|
||||
"Expected {path} to contain a gitdir pointer, but it did not match `gitdir: <path>`.",
|
||||
path = dot_git.display()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
74
codex-rs/linux-sandbox/src/vendored_bwrap.rs
Normal file
74
codex-rs/linux-sandbox/src/vendored_bwrap.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! 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;
|
||||
}
|
||||
|
||||
fn argv_to_cstrings(argv: &[String]) -> Vec<CString> {
|
||||
let mut cstrings: Vec<CString> = 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}"),
|
||||
}
|
||||
}
|
||||
cstrings
|
||||
}
|
||||
|
||||
/// Run the build-time bubblewrap `main` function and return its exit code.
|
||||
///
|
||||
/// On success, bubblewrap will `execve` into the target program and this
|
||||
/// function will never return. A return value therefore implies failure.
|
||||
pub(crate) fn run_vendored_bwrap_main(argv: &[String]) -> libc::c_int {
|
||||
let cstrings = argv_to_cstrings(argv);
|
||||
|
||||
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.
|
||||
unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Execute the build-time bubblewrap `main` function with the given argv.
|
||||
pub(crate) fn exec_vendored_bwrap(argv: Vec<String>) -> ! {
|
||||
let exit_code = run_vendored_bwrap_main(&argv);
|
||||
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 run_vendored_bwrap_main(_argv: &[String]) -> libc::c_int {
|
||||
panic!(
|
||||
"build-time bubblewrap is not available in this build.\n\
|
||||
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)"
|
||||
);
|
||||
}
|
||||
|
||||
/// Panics with a clear error when the build-time bwrap path is not enabled.
|
||||
pub(crate) fn exec_vendored_bwrap(_argv: Vec<String>) -> ! {
|
||||
let _ = run_vendored_bwrap_main(&[]);
|
||||
unreachable!("run_vendored_bwrap_main should always panic in this configuration")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use imp::exec_vendored_bwrap;
|
||||
pub(crate) use imp::run_vendored_bwrap_main;
|
||||
@@ -2,6 +2,7 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use codex_core::config::types::ShellEnvironmentPolicy;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::error::Result;
|
||||
use codex_core::error::SandboxErr;
|
||||
use codex_core::exec::ExecParams;
|
||||
use codex_core::exec::process_exec_tool_call;
|
||||
@@ -47,12 +48,23 @@ async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::expect_used, clippy::unwrap_used)]
|
||||
#[expect(clippy::expect_used)]
|
||||
async fn run_cmd_output(
|
||||
cmd: &[&str],
|
||||
writable_roots: &[PathBuf],
|
||||
timeout_ms: u64,
|
||||
) -> codex_core::exec::ExecToolCallOutput {
|
||||
run_cmd_result(cmd, writable_roots, timeout_ms)
|
||||
.await
|
||||
.expect("sandboxed command should succeed")
|
||||
}
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
async fn run_cmd_result(
|
||||
cmd: &[&str],
|
||||
writable_roots: &[PathBuf],
|
||||
timeout_ms: u64,
|
||||
) -> Result<codex_core::exec::ExecToolCallOutput> {
|
||||
let cwd = std::env::current_dir().expect("cwd should exist");
|
||||
let sandbox_cwd = cwd.clone();
|
||||
let params = ExecParams {
|
||||
@@ -86,10 +98,24 @@ async fn run_cmd_output(
|
||||
&sandbox_policy,
|
||||
sandbox_cwd.as_path(),
|
||||
&codex_linux_sandbox_exe,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn expect_denied(
|
||||
result: Result<codex_core::exec::ExecToolCallOutput>,
|
||||
context: &str,
|
||||
) -> codex_core::exec::ExecToolCallOutput {
|
||||
match result {
|
||||
Ok(output) => {
|
||||
assert_ne!(output.exit_code, 0, "{context}: expected nonzero exit code");
|
||||
output
|
||||
}
|
||||
Err(CodexErr::Sandbox(SandboxErr::Denied { output })) => *output,
|
||||
Err(err) => panic!("{context}: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -192,6 +218,7 @@ async fn assert_network_blocked(cmd: &[&str]) {
|
||||
&sandbox_policy,
|
||||
sandbox_cwd.as_path(),
|
||||
&codex_linux_sandbox_exe,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
@@ -242,6 +269,77 @@ async fn sandbox_blocks_nc() {
|
||||
assert_network_blocked(&["nc", "-z", "127.0.0.1", "80"]).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sandbox_blocks_git_and_codex_writes_inside_writable_root() {
|
||||
let tmpdir = tempfile::tempdir().expect("tempdir");
|
||||
let dot_git = tmpdir.path().join(".git");
|
||||
let dot_codex = tmpdir.path().join(".codex");
|
||||
std::fs::create_dir_all(&dot_git).expect("create .git");
|
||||
std::fs::create_dir_all(&dot_codex).expect("create .codex");
|
||||
|
||||
let git_target = dot_git.join("config");
|
||||
let codex_target = dot_codex.join("config.toml");
|
||||
|
||||
let git_output = expect_denied(
|
||||
run_cmd_result(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
&format!("echo denied > {}", git_target.to_string_lossy()),
|
||||
],
|
||||
&[tmpdir.path().to_path_buf()],
|
||||
LONG_TIMEOUT_MS,
|
||||
)
|
||||
.await,
|
||||
".git write should be denied under bubblewrap",
|
||||
);
|
||||
|
||||
let codex_output = expect_denied(
|
||||
run_cmd_result(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
&format!("echo denied > {}", codex_target.to_string_lossy()),
|
||||
],
|
||||
&[tmpdir.path().to_path_buf()],
|
||||
LONG_TIMEOUT_MS,
|
||||
)
|
||||
.await,
|
||||
".codex write should be denied under bubblewrap",
|
||||
);
|
||||
assert_ne!(git_output.exit_code, 0);
|
||||
assert_ne!(codex_output.exit_code, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sandbox_blocks_codex_symlink_replacement_attack() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let tmpdir = tempfile::tempdir().expect("tempdir");
|
||||
let decoy = tmpdir.path().join("decoy-codex");
|
||||
std::fs::create_dir_all(&decoy).expect("create decoy dir");
|
||||
|
||||
let dot_codex = tmpdir.path().join(".codex");
|
||||
symlink(&decoy, &dot_codex).expect("create .codex symlink");
|
||||
|
||||
let codex_target = dot_codex.join("config.toml");
|
||||
|
||||
let codex_output = expect_denied(
|
||||
run_cmd_result(
|
||||
&[
|
||||
"bash",
|
||||
"-lc",
|
||||
&format!("echo denied > {}", codex_target.to_string_lossy()),
|
||||
],
|
||||
&[tmpdir.path().to_path_buf()],
|
||||
LONG_TIMEOUT_MS,
|
||||
)
|
||||
.await,
|
||||
".codex symlink replacement should be denied",
|
||||
);
|
||||
assert_ne!(codex_output.exit_code, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sandbox_blocks_ssh() {
|
||||
// Force ssh to attempt a real TCP connection but fail quickly. `BatchMode`
|
||||
|
||||
1
codex-rs/vendor/bubblewrap/.dir-locals.el
vendored
Normal file
1
codex-rs/vendor/bubblewrap/.dir-locals.el
vendored
Normal file
@@ -0,0 +1 @@
|
||||
((c-mode . ((indent-tabs-mode . nil) (c-file-style . "gnu"))))
|
||||
5
codex-rs/vendor/bubblewrap/.editorconfig
vendored
Normal file
5
codex-rs/vendor/bubblewrap/.editorconfig
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[*.[ch]]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
indent_brace_style = gnu
|
||||
105
codex-rs/vendor/bubblewrap/.github/workflows/check.yml
vendored
Normal file
105
codex-rs/vendor/bubblewrap/.github/workflows/check.yml
vendored
Normal file
@@ -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
|
||||
3
codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md
vendored
Normal file
3
codex-rs/vendor/bubblewrap/CODE-OF-CONDUCT.md
vendored
Normal file
@@ -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).
|
||||
481
codex-rs/vendor/bubblewrap/COPYING
vendored
Normal file
481
codex-rs/vendor/bubblewrap/COPYING
vendored
Normal file
@@ -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.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
1
codex-rs/vendor/bubblewrap/LICENSE
vendored
Symbolic link
1
codex-rs/vendor/bubblewrap/LICENSE
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
COPYING
|
||||
51
codex-rs/vendor/bubblewrap/NEWS.md
vendored
Normal file
51
codex-rs/vendor/bubblewrap/NEWS.md
vendored
Normal file
@@ -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 `<stdbool.h>` for booleans (#660, Simon McVittie)
|
||||
|
||||
* Avoid `-Wshadow` compiler warnings (#661, Simon McVittie)
|
||||
|
||||
* Update Github Actions configuration (#658, Simon McVittie)
|
||||
|
||||
----
|
||||
|
||||
See also <https://github.com/containers/bubblewrap/releases>
|
||||
255
codex-rs/vendor/bubblewrap/README.md
vendored
Normal file
255
codex-rs/vendor/bubblewrap/README.md
vendored
Normal file
@@ -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 cat by [dancing_stupidity](https://www.flickr.com/photos/27549668@N03/))
|
||||
42
codex-rs/vendor/bubblewrap/SECURITY.md
vendored
Normal file
42
codex-rs/vendor/bubblewrap/SECURITY.md
vendored
Normal file
@@ -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.
|
||||
598
codex-rs/vendor/bubblewrap/bind-mount.c
vendored
Normal file
598
codex-rs/vendor/bubblewrap/bind-mount.c
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
54
codex-rs/vendor/bubblewrap/bind-mount.h
vendored
Normal file
54
codex-rs/vendor/bubblewrap/bind-mount.h
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#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)));
|
||||
3641
codex-rs/vendor/bubblewrap/bubblewrap.c
vendored
Normal file
3641
codex-rs/vendor/bubblewrap/bubblewrap.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codex-rs/vendor/bubblewrap/bubblewrap.jpg
vendored
Normal file
BIN
codex-rs/vendor/bubblewrap/bubblewrap.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
648
codex-rs/vendor/bubblewrap/bwrap.xml
vendored
Normal file
648
codex-rs/vendor/bubblewrap/bwrap.xml
vendored
Normal file
@@ -0,0 +1,648 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
|
||||
]>
|
||||
<refentry id="bwrap">
|
||||
|
||||
<refentryinfo>
|
||||
<title>bwrap</title>
|
||||
<productname>Containers</productname>
|
||||
<authorgroup>
|
||||
<author>
|
||||
<contrib>Developer</contrib>
|
||||
<firstname>Alexander</firstname>
|
||||
<surname>Larsson</surname>
|
||||
</author>
|
||||
<author>
|
||||
<contrib>Developer</contrib>
|
||||
<firstname>Colin</firstname>
|
||||
<surname>Walters</surname>
|
||||
</author>
|
||||
</authorgroup>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>bwrap</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
<refmiscinfo class="manual">User Commands</refmiscinfo>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>bwrap</refname>
|
||||
<refpurpose>container setup utility</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>bwrap</command>
|
||||
<arg choice="opt" rep="repeat"><replaceable>OPTION</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>COMMAND</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1><title>Description</title>
|
||||
<para>
|
||||
<command>bwrap</command> 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.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
By default, <command>bwrap</command> 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.
|
||||
</para>
|
||||
<para>
|
||||
If needed (e.g. when using a PID namespace) <command>bwrap</command>
|
||||
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.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1><title>Options</title>
|
||||
<para>
|
||||
When options are used multiple times, the last option wins, unless otherwise
|
||||
specified.
|
||||
</para>
|
||||
<para>General options:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--help</option></term>
|
||||
<listitem><para>Print help and exit</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--version</option></term>
|
||||
<listitem><para>Print version</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--args <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Parse nul-separated arguments from the given file descriptor.
|
||||
This option can be used multiple times to parse options from
|
||||
multiple sources.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--argv0 <arg choice="plain">VALUE</arg></option></term>
|
||||
<listitem><para>Set argv[0] to the value <arg choice="plain">VALUE</arg> before running the program</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--level-prefix</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
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
|
||||
<citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>:
|
||||
for example, <literal><4></literal> indicates a warning,
|
||||
because <literal>LOG_WARNING</literal> 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
|
||||
<literal>logger --prio-prefix</literal> (see
|
||||
<citerefentry><refentrytitle>logger</refentrytitle><manvolnum>1</manvolnum></citerefentry>)
|
||||
or <literal>systemd-cat --level-prefix=1</literal> (see
|
||||
<citerefentry><refentrytitle>systemd-cat</refentrytitle><manvolnum>1</manvolnum></citerefentry>).
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>Options related to kernel namespaces:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-user</option></term>
|
||||
<listitem><para>Create a new user namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-user-try</option></term>
|
||||
<listitem><para>Create a new user namespace if possible else skip it</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-ipc</option></term>
|
||||
<listitem><para>Create a new ipc namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-pid</option></term>
|
||||
<listitem><para>Create a new pid namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-net</option></term>
|
||||
<listitem><para>Create a new network namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-uts</option></term>
|
||||
<listitem><para>Create a new uts namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-cgroup</option></term>
|
||||
<listitem><para>Create a new cgroup namespace</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-cgroup-try</option></term>
|
||||
<listitem><para>Create a new cgroup namespace if possible else skip it</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unshare-all</option></term>
|
||||
<listitem><para>Unshare all possible namespaces. Currently equivalent with: <option>--unshare-user-try</option> <option>--unshare-ipc</option> <option>--unshare-pid</option> <option>--unshare-net</option> <option>--unshare-uts</option> <option>--unshare-cgroup-try</option></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--share-net</option></term>
|
||||
<listitem><para>Retain the network namespace, overriding an earlier <option>--unshare-all</option> or <option>--unshare-net</option></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--userns <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>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. </para>
|
||||
<para>This is incompatible with --unshare-user, and doesn't work in the setuid version of bubblewrap.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--userns2 <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>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.</para>
|
||||
<para>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.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--disable-userns</option></term>
|
||||
<listitem><para>
|
||||
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
|
||||
<literal>user.max_user_namespaces</literal> sysctl to 1, and then
|
||||
entering a nested user namespace which is unable to raise that limit
|
||||
in the outer namespace.
|
||||
This option requires <option>--unshare-user</option>, and doesn't work
|
||||
in the setuid version of bubblewrap.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--assert-userns-disabled</option></term>
|
||||
<listitem><para>
|
||||
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
|
||||
<option>--userns</option> to check that the given user namespace
|
||||
has already been set up to prevent the creation of further user
|
||||
namespaces.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--pidns <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>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. </para>
|
||||
<para>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.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--uid <arg choice="plain">UID</arg></option></term>
|
||||
<listitem><para>Use a custom user id in the sandbox (requires <option>--unshare-user</option>)</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--gid <arg choice="plain">GID</arg></option></term>
|
||||
<listitem><para>Use a custom group id in the sandbox (requires <option>--unshare-user</option>)</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--hostname <arg choice="plain">HOSTNAME</arg></option></term>
|
||||
<listitem><para>Use a custom hostname in the sandbox (requires <option>--unshare-uts</option>)</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>Options about environment setup:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--chdir <arg choice="plain">DIR</arg></option></term>
|
||||
<listitem><para>Change directory to <arg choice="plain">DIR</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--setenv <arg choice="plain">VAR</arg> <arg choice="plain">VALUE</arg></option></term>
|
||||
<listitem><para>Set an environment variable</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--unsetenv <arg choice="plain">VAR</arg></option></term>
|
||||
<listitem><para>Unset an environment variable</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--clearenv</option></term>
|
||||
<listitem><para>Unset all environment variables, except for
|
||||
<envar>PWD</envar> and any that are subsequently set by
|
||||
<option>--setenv</option></para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>Options for monitoring the sandbox from the outside:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--lock-file <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>
|
||||
Take a lock on <arg choice="plain">DEST</arg> while the sandbox is running.
|
||||
This option can be used multiple times to take locks on multiple files.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--sync-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>Keep this file descriptor open while the sandbox is running</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
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>--perms</option> 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.
|
||||
<option>--size</option> modifies the size of the created mount when preceding a
|
||||
<option>--tmpfs</option> action; <option>--perms</option> and <option>--size</option>
|
||||
can be combined.
|
||||
</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--perms <arg choice="plain">OCTAL</arg></option></term>
|
||||
<listitem><para>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 <arg choice="plain">OCTAL</arg>.
|
||||
Subsequent operations are not affected: for example,
|
||||
<literal>--perms 0700 --tmpfs /a --tmpfs /b</literal> will mount
|
||||
<filename>/a</filename> with permissions 0700, then return to
|
||||
the default permissions for <filename>/b</filename>.
|
||||
Note that <option>--perms</option> and <option>--size</option> can be
|
||||
combined: <literal>--perms 0700 --size 10485760 --tmpfs /s</literal> will apply
|
||||
permissions as well as a maximum size to the created tmpfs.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--size <arg choice="plain">BYTES</arg></option></term>
|
||||
<listitem><para>This option does nothing on its own, and must be followed
|
||||
by <literal>--tmpfs</literal>. It sets the size in bytes for the next tmpfs.
|
||||
For example, <literal>--size 10485760 --tmpfs /tmp</literal> will create a tmpfs
|
||||
at <filename>/tmp</filename> of size 10MiB. Subsequent operations are not
|
||||
affected: for example,
|
||||
<literal>--size 10485760 --tmpfs /a --tmpfs /b</literal> will mount
|
||||
<filename>/a</filename> with size 10MiB, then return to the default size for
|
||||
<filename>/b</filename>.
|
||||
Note that <option>--perms</option> and <option>--size</option> can be
|
||||
combined: <literal>--size 10485760 --perms 0700 --tmpfs /s</literal> will apply
|
||||
permissions as well as a maximum size to the created tmpfs.</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--bind <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Bind mount the host path <arg choice="plain">SRC</arg> on <arg choice="plain">DEST</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--bind-try <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Equal to <option>--bind</option> but ignores non-existent <arg choice="plain">SRC</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--dev-bind <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Bind mount the host path <arg choice="plain">SRC</arg> on <arg choice="plain">DEST</arg>, allowing device access</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--dev-bind-try <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Equal to <option>--dev-bind</option> but ignores non-existent <arg choice="plain">SRC</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--ro-bind <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Bind mount the host path <arg choice="plain">SRC</arg> readonly on <arg choice="plain">DEST</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--ro-bind-try <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Equal to <option>--ro-bind</option> but ignores non-existent <arg choice="plain">SRC</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--remount-ro <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Remount the path <arg choice="plain">DEST</arg> as readonly. It works only on the specified mount point, without changing any other mount point under the specified path</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--overlay-src <arg choice="plain">SRC</arg></option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
This option does nothing on its own, and must be followed by one of
|
||||
the other <literal>overlay</literal> options. It specifies a host
|
||||
path from which files should be read if they aren't present in a
|
||||
higher layer.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
(For readers familiar with overlayfs, note that this is the
|
||||
reverse of the order used by the kernel's <literal>lowerdir</literal>
|
||||
mount option.)
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--overlay <arg choice="plain">RWSRC</arg> <arg choice="plain">WORKDIR</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tmp-overlay <arg choice="plain">DEST</arg></option></term>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--ro-overlay <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Use overlayfs to mount the host paths specified by
|
||||
<arg choice="plain">RWSRC</arg> and all immediately preceding
|
||||
<option>--overlay-src</option> on <arg choice="plain">DEST</arg>.
|
||||
<arg choice="plain">DEST</arg> will contain the union of all the files
|
||||
in all the layers.
|
||||
</para>
|
||||
<para>
|
||||
With <arg choice="plain">--overlay</arg> all writes will go to
|
||||
<arg choice="plain">RWSRC</arg>. Reads will come preferentially from
|
||||
<arg choice="plain">RWSRC</arg>, and then from any
|
||||
<option>--overlay-src</option> paths.
|
||||
<arg choice="plain">WORKDIR</arg> must be an empty directory on the
|
||||
same filesystem as <arg choice="plain">RWSRC</arg>, and is used
|
||||
internally by the kernel.
|
||||
</para>
|
||||
<para>
|
||||
With <arg choice="plain">--tmp-overlay</arg> 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.
|
||||
</para>
|
||||
<para>
|
||||
With <arg choice="plain">--ro-overlay</arg> the filesystem will be
|
||||
mounted read-only. This option requires at least two
|
||||
<option>--overlay-src</option> to precede it.
|
||||
</para>
|
||||
<para>
|
||||
None of these options are available in the setuid version of
|
||||
bubblewrap. Using <arg choice="plain">--ro-overlay</arg> or providing
|
||||
more than one <option>--overlay-src</option> requires a Linux kernel
|
||||
version of 4.0 or later.
|
||||
</para>
|
||||
<para>
|
||||
Due to limitations of overlayfs, no host directory given via
|
||||
<arg choice="plain">--overlay-src</arg> or
|
||||
<arg choice="plain">--overlay</arg> 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.
|
||||
</para>
|
||||
<para>
|
||||
For more information see the Overlay Filesystem documentation in the
|
||||
Linux kernel at
|
||||
https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--proc <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Mount procfs on <arg choice="plain">DEST</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--dev <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Mount new devtmpfs on <arg choice="plain">DEST</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--tmpfs <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Mount new tmpfs on <arg choice="plain">DEST</arg>.
|
||||
If the previous option was <option>--perms</option>, it sets the
|
||||
mode of the tmpfs. Otherwise, the tmpfs has mode 0755.
|
||||
If the previous option was <option>--size</option>, it sets the
|
||||
size in bytes of the tmpfs. Otherwise, the tmpfs has the default size.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--mqueue <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem><para>Mount new mqueue on <arg choice="plain">DEST</arg></para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--dir <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Create a directory at <arg choice="plain">DEST</arg>.
|
||||
If the directory already exists, its permissions are unmodified,
|
||||
ignoring <option>--perms</option> (use <option>--chmod</option>
|
||||
if the permissions of an existing directory need to be changed).
|
||||
If the directory is newly created and the previous option was
|
||||
<option>--perms</option>, it sets the mode of the directory.
|
||||
Otherwise, newly-created directories have mode 0755.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--file <arg choice="plain">FD</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Copy from the file descriptor <arg choice="plain">FD</arg> to
|
||||
<arg choice="plain">DEST</arg>.
|
||||
If the previous option was <option>--perms</option>, it sets the
|
||||
mode of the new file. Otherwise, the file has mode 0666
|
||||
(note that this is not the same as <option>--bind-data</option>).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--bind-data <arg choice="plain">FD</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Copy from the file descriptor <arg choice="plain">FD</arg> to
|
||||
a file which is bind-mounted on <arg choice="plain">DEST</arg>.
|
||||
If the previous option was <option>--perms</option>, it sets the
|
||||
mode of the new file. Otherwise, the file has mode 0600
|
||||
(note that this is not the same as <option>--file</option>).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--ro-bind-data <arg choice="plain">FD</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Copy from the file descriptor <arg choice="plain">FD</arg> to
|
||||
a file which is bind-mounted read-only on
|
||||
<arg choice="plain">DEST</arg>.
|
||||
If the previous option was <option>--perms</option>, it sets the
|
||||
mode of the new file. Otherwise, the file has mode 0600
|
||||
(note that this is not the same as <option>--file</option>).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--symlink <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
|
||||
<listitem>
|
||||
<para>Create a symlink at <arg choice="plain">DEST</arg> with target
|
||||
<arg choice="plain">SRC</arg>.</para>
|
||||
<para>Since version 0.9.0, it is not considered to be an error if
|
||||
<arg choice="plain">DEST</arg> already exists as a symbolic link and its
|
||||
target is exactly <arg choice="plain">SRC</arg>.</para>
|
||||
<para>Before version 0.9.0, if <arg choice="plain">DEST</arg> already
|
||||
existed, this would be treated as an error (even if its target
|
||||
was identical to <arg choice="plain">SRC</arg>).</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--chmod <arg choice="plain">OCTAL</arg> <arg choice="plain">PATH</arg></option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Set the permissions of <arg choice="plain">PATH</arg>, which
|
||||
must already exist, to <arg choice="plain">OCTAL</arg>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<para>Lockdown options:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--seccomp <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Load and use seccomp rules from <arg choice="plain">FD</arg>.
|
||||
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 <option>--add-seccomp-fd</option> if multiple seccomp programs
|
||||
are needed.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--add-seccomp-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Load and use seccomp rules from <arg choice="plain">FD</arg>.
|
||||
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 <option>--seccomp</option>.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--exec-label <arg choice="plain">LABEL</arg></option></term>
|
||||
<listitem><para>
|
||||
Exec Label from the sandbox. On an SELinux system you can specify the SELinux
|
||||
context for the sandbox process(s).
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--file-label <arg choice="plain">LABEL</arg></option></term>
|
||||
<listitem><para>
|
||||
File label for temporary sandbox content. On an SELinux system you can specify
|
||||
the SELinux context for the sandbox content.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--block-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Block the sandbox on reading from FD until some data is available.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--userns-block-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
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.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--info-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Write information in JSON format about the sandbox to FD.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--json-status-fd <arg choice="plain">FD</arg></option></term>
|
||||
<listitem><para>
|
||||
Multiple JSON documents are written to <arg choice="plain">FD</arg>,
|
||||
one per line (<ulink url="https://jsonlines.org/">"JSON lines" format</ulink>).
|
||||
Each line is a single JSON object.
|
||||
After <command>bwrap</command> has started the child process inside the sandbox,
|
||||
it writes an object with a <literal>child-pid</literal> member to the
|
||||
<option>--json-status-fd</option> (this duplicates the older <option>--info-fd</option>).
|
||||
The corresponding value is the process ID of the child process in the pid namespace from
|
||||
which <command>bwrap</command> was run.
|
||||
If available, the namespace IDs are also included in the object with the <literal>child-pid</literal>;
|
||||
again, this duplicates the older <option>--info-fd</option>.
|
||||
When the child process inside the sandbox exits, <command>bwrap</command> writes an object
|
||||
with an exit-code member, and then closes the <option>--json-status-fd</option>. The value
|
||||
corresponding to <literal>exit-code</literal> 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 <command>bwrap</command>,
|
||||
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.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--new-session</option></term>
|
||||
<listitem><para>
|
||||
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.
|
||||
</para><para>
|
||||
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).
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--die-with-parent</option></term>
|
||||
<listitem><para>
|
||||
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.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--as-pid-1</option></term>
|
||||
<listitem><para>
|
||||
Do not create a process with PID=1 in the sandbox to reap child processes.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--cap-add <arg choice="plain">CAP</arg></option></term>
|
||||
<listitem><para>
|
||||
Add the specified capability <arg choice="plain">CAP</arg>, e.g.
|
||||
CAP_DAC_READ_SEARCH, when running as privileged user. It accepts
|
||||
the special value ALL to add all the permitted caps.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--cap-drop <arg choice="plain">CAP</arg></option></term>
|
||||
<listitem><para>
|
||||
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
|
||||
<option>--cap-add</option> and <option>--cap-drop</option>
|
||||
options are processed in the order they are specified on the
|
||||
command line. Please be careful to the order they are specified.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Environment</title>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><envar>HOME</envar></term>
|
||||
<listitem><para>
|
||||
Used as the cwd in the sandbox if <option>--chdir</option> has not been
|
||||
explicitly specified and the current cwd is not present inside the sandbox.
|
||||
The <option>--setenv</option> option can be used to override the value
|
||||
that is used here.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>
|
||||
The <command>bwrap</command> command returns the exit status of the
|
||||
initial application process (pid 2 in the sandbox).
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
107
codex-rs/vendor/bubblewrap/ci/builddeps.sh
vendored
Executable file
107
codex-rs/vendor/bubblewrap/ci/builddeps.sh
vendored
Executable file
@@ -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 <<EOF
|
||||
Usage: see source code
|
||||
EOF
|
||||
exit "${1-2}"
|
||||
}
|
||||
|
||||
opt_clang=
|
||||
|
||||
getopt_temp="help"
|
||||
getopt_temp="$getopt_temp,clang"
|
||||
|
||||
getopt_temp="$(getopt -o '' --long "${getopt_temp}" -n "$0" -- "$@")"
|
||||
eval set -- "$getopt_temp"
|
||||
unset getopt_temp
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
(--clang)
|
||||
clang=yes
|
||||
shift
|
||||
;;
|
||||
|
||||
(--help)
|
||||
usage 0
|
||||
# not reached
|
||||
;;
|
||||
|
||||
(--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
|
||||
(*)
|
||||
echo 'Error parsing options' >&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:
|
||||
6
codex-rs/vendor/bubblewrap/ci/enable-userns.sh
vendored
Executable file
6
codex-rs/vendor/bubblewrap/ci/enable-userns.sh
vendored
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "kernel.apparmor_restrict_unprivileged_userns = 0" > /etc/sysctl.d/99-userns.conf
|
||||
sysctl --system
|
||||
80
codex-rs/vendor/bubblewrap/completions/bash/bwrap
vendored
Normal file
80
codex-rs/vendor/bubblewrap/completions/bash/bwrap
vendored
Normal file
@@ -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:
|
||||
35
codex-rs/vendor/bubblewrap/completions/bash/meson.build
vendored
Normal file
35
codex-rs/vendor/bubblewrap/completions/bash/meson.build
vendored
Normal file
@@ -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)
|
||||
7
codex-rs/vendor/bubblewrap/completions/meson.build
vendored
Normal file
7
codex-rs/vendor/bubblewrap/completions/meson.build
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
if get_option('bash_completion').enabled()
|
||||
subdir('bash')
|
||||
endif
|
||||
|
||||
if get_option('zsh_completion').enabled()
|
||||
subdir('zsh')
|
||||
endif
|
||||
115
codex-rs/vendor/bubblewrap/completions/zsh/_bwrap
vendored
Normal file
115
codex-rs/vendor/bubblewrap/completions/zsh/_bwrap
vendored
Normal file
@@ -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
|
||||
}
|
||||
7
codex-rs/vendor/bubblewrap/completions/zsh/meson.build
vendored
Normal file
7
codex-rs/vendor/bubblewrap/completions/zsh/meson.build
vendored
Normal file
@@ -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)
|
||||
34
codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh
vendored
Executable file
34
codex-rs/vendor/bubblewrap/demos/bubblewrap-shell.sh
vendored
Executable file
@@ -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)
|
||||
65
codex-rs/vendor/bubblewrap/demos/flatpak-run.sh
vendored
Executable file
65
codex-rs/vendor/bubblewrap/demos/flatpak-run.sh
vendored
Executable file
@@ -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<<EOF
|
||||
[Application]
|
||||
name=org.gnome.Weather
|
||||
runtime=runtime/org.gnome.Platform/x86_64/master
|
||||
EOF
|
||||
BIN
codex-rs/vendor/bubblewrap/demos/flatpak.bpf
vendored
Normal file
BIN
codex-rs/vendor/bubblewrap/demos/flatpak.bpf
vendored
Normal file
Binary file not shown.
39
codex-rs/vendor/bubblewrap/demos/userns-block-fd.py
vendored
Executable file
39
codex-rs/vendor/bubblewrap/demos/userns-block-fd.py
vendored
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os, select, subprocess, sys, json
|
||||
|
||||
pipe_info = os.pipe()
|
||||
userns_block = os.pipe()
|
||||
|
||||
pid = os.fork()
|
||||
|
||||
if pid != 0:
|
||||
os.close(pipe_info[1])
|
||||
os.close(userns_block[0])
|
||||
|
||||
select.select([pipe_info[0]], [], [])
|
||||
|
||||
data = json.load(os.fdopen(pipe_info[0]))
|
||||
child_pid = str(data['child-pid'])
|
||||
|
||||
subprocess.call(["newuidmap", child_pid, "0", str(os.getuid()), "1"])
|
||||
subprocess.call(["newgidmap", child_pid, "0", str(os.getgid()), "1"])
|
||||
|
||||
os.write(userns_block[1], b'1')
|
||||
else:
|
||||
os.close(pipe_info[0])
|
||||
os.close(userns_block[1])
|
||||
|
||||
os.set_inheritable(pipe_info[1], True)
|
||||
os.set_inheritable(userns_block[0], True)
|
||||
|
||||
args = ["bwrap",
|
||||
"bwrap",
|
||||
"--unshare-all",
|
||||
"--unshare-user",
|
||||
"--userns-block-fd", "%i" % userns_block[0],
|
||||
"--info-fd", "%i" % pipe_info[1],
|
||||
"--bind", "/", "/",
|
||||
"cat", "/proc/self/uid_map"]
|
||||
|
||||
os.execlp(*args)
|
||||
171
codex-rs/vendor/bubblewrap/meson.build
vendored
Normal file
171
codex-rs/vendor/bubblewrap/meson.build
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
project(
|
||||
'bubblewrap',
|
||||
'c',
|
||||
version : '0.11.0',
|
||||
meson_version : '>=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
|
||||
73
codex-rs/vendor/bubblewrap/meson_options.txt
vendored
Normal file
73
codex-rs/vendor/bubblewrap/meson_options.txt
vendored
Normal file
@@ -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 : '',
|
||||
)
|
||||
199
codex-rs/vendor/bubblewrap/network.c
vendored
Normal file
199
codex-rs/vendor/bubblewrap/network.c
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/loop.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#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 = { .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,
|
||||
(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 = { .nl_family = AF_NETLINK, .nl_pid = 0, .nl_groups = 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");
|
||||
}
|
||||
22
codex-rs/vendor/bubblewrap/network.h
vendored
Normal file
22
codex-rs/vendor/bubblewrap/network.h
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void loopback_setup (void);
|
||||
48
codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec
vendored
Normal file
48
codex-rs/vendor/bubblewrap/packaging/bubblewrap.spec
vendored
Normal file
@@ -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/*
|
||||
18
codex-rs/vendor/bubblewrap/release-checklist.md
vendored
Normal file
18
codex-rs/vendor/bubblewrap/release-checklist.md
vendored
Normal file
@@ -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`
|
||||
190
codex-rs/vendor/bubblewrap/tests/libtest-core.sh
vendored
Normal file
190
codex-rs/vendor/bubblewrap/tests/libtest-core.sh
vendored
Normal file
@@ -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 <walters@verbum.org>
|
||||
#
|
||||
# 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
|
||||
115
codex-rs/vendor/bubblewrap/tests/libtest.sh
vendored
Normal file
115
codex-rs/vendor/bubblewrap/tests/libtest.sh
vendored
Normal file
@@ -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 <walters@verbum.org>
|
||||
# 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/"
|
||||
}
|
||||
72
codex-rs/vendor/bubblewrap/tests/meson.build
vendored
Normal file
72
codex-rs/vendor/bubblewrap/tests/meson.build
vendored
Normal file
@@ -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
|
||||
692
codex-rs/vendor/bubblewrap/tests/test-run.sh
vendored
Executable file
692
codex-rs/vendor/bubblewrap/tests/test-run.sh
vendored
Executable file
@@ -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 <<EOF
|
||||
#!/usr/bin/env python3
|
||||
import struct,fcntl,sys
|
||||
path = sys.argv[1]
|
||||
if sys.argv[2] == 'wait':
|
||||
locktype = fcntl.F_SETLKW
|
||||
else:
|
||||
locktype = fcntl.F_SETLK
|
||||
lockdata = struct.pack("hhqqhh", fcntl.F_WRLCK, 0, 0, 0, 0, 0)
|
||||
fd=open(sys.argv[1], 'a')
|
||||
try:
|
||||
fcntl.fcntl(fd.fileno(), locktype, lockdata)
|
||||
except IOError as e:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
EOF
|
||||
chmod a+x lockf-n.py
|
||||
touch lock
|
||||
|
||||
for die_with_parent_argv in "--die-with-parent" "--die-with-parent --unshare-pid"; do
|
||||
# We have to loop here, because bwrap doesn't wait for the lock if
|
||||
# another process is holding it. If we're unlucky, lockf-n.py will
|
||||
# be holding it.
|
||||
bash -c "while true; do $RUN ${die_with_parent_argv} --lock-file $(pwd)/lock sleep 1h; done" &
|
||||
childshellpid=$!
|
||||
|
||||
# Wait for lock to be taken (yes hacky)
|
||||
for x in $(seq 10); do
|
||||
if ./lockf-n.py ./lock nowait; then
|
||||
sleep 1
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
if ./lockf-n.py ./lock nowait; then
|
||||
assert_not_reached "timed out waiting for lock"
|
||||
fi
|
||||
|
||||
# Kill the shell, which should kill bwrap (and the sleep)
|
||||
kill -9 ${childshellpid}
|
||||
# Lock file should be unlocked
|
||||
./lockf-n.py ./lock wait
|
||||
ok "die with parent ${die_with_parent_argv}"
|
||||
done
|
||||
|
||||
printf '%s--dir\0/tmp/hello/world\0' '' > 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<test.args 4<test.args2 5<test.args3
|
||||
ok "we can parse arguments from a fd"
|
||||
|
||||
mkdir bin
|
||||
echo "#!/bin/sh" > 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
|
||||
635
codex-rs/vendor/bubblewrap/tests/test-seccomp.py
vendored
Executable file
635
codex-rs/vendor/bubblewrap/tests/test-seccomp.py
vendored
Executable file
@@ -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()
|
||||
28
codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh
vendored
Executable file
28
codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh
vendored
Executable file
@@ -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
|
||||
28
codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh
vendored
Executable file
28
codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh
vendored
Executable file
@@ -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
|
||||
247
codex-rs/vendor/bubblewrap/tests/test-utils.c
vendored
Normal file
247
codex-rs/vendor/bubblewrap/tests/test-utils.c
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
180
codex-rs/vendor/bubblewrap/tests/try-syscall.c
vendored
Normal file
180
codex-rs/vendor/bubblewrap/tests/try-syscall.c
vendored
Normal file
@@ -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 <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
2
codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore
vendored
Normal file
2
codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/_build/
|
||||
/subprojects/
|
||||
3
codex-rs/vendor/bubblewrap/tests/use-as-subproject/README
vendored
Normal file
3
codex-rs/vendor/bubblewrap/tests/use-as-subproject/README
vendored
Normal file
@@ -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.
|
||||
26
codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py
vendored
Executable file
26
codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py
vendored
Executable file
@@ -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
|
||||
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h
vendored
Normal file
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#error Should not use superproject config.h to compile bubblewrap
|
||||
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in
vendored
Normal file
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#error Should not use superproject generated config.h to compile bubblewrap
|
||||
20
codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build
vendored
Normal file
20
codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build
vendored
Normal file
@@ -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-',
|
||||
],
|
||||
)
|
||||
136
codex-rs/vendor/bubblewrap/uncrustify.cfg
vendored
Normal file
136
codex-rs/vendor/bubblewrap/uncrustify.cfg
vendored
Normal file
@@ -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
|
||||
2
codex-rs/vendor/bubblewrap/uncrustify.sh
vendored
Executable file
2
codex-rs/vendor/bubblewrap/uncrustify.sh
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
uncrustify -c uncrustify.cfg --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$`
|
||||
1080
codex-rs/vendor/bubblewrap/utils.c
vendored
Normal file
1080
codex-rs/vendor/bubblewrap/utils.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
217
codex-rs/vendor/bubblewrap/utils.h
vendored
Normal file
217
codex-rs/vendor/bubblewrap/utils.h
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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);
|
||||
Reference in New Issue
Block a user