Skip to content

Commit 61c4bb7

Browse files
committed
Limit permissions for Android images.
Remove the use of the `--privileged` flag for Android images and instead use an seccomp permissions. The provided profile is derived from the docker documentation, with slight modifications to allow `clone` and `clone3`. The documentation is [docker seccomp](https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile), which details the syscalls blocked by docker. The same is true for podman. We merely modified these settings to allow `personality` syscall, which then allows us to use our Android images. On Windows with Docker Desktop, we currently have an issue where Docker tries to read the seccomp profile, and then interpret that as the path, rather than load the profile from the path, which is tracked by the following issue: docker/for-win#12760 On Podman (not inside WSL2), we have a separate issue where it expects a WSL path to be provided for the seccomp profile, despite the path being provided for the host.
1 parent 94adafe commit 61c4bb7

File tree

5 files changed

+224
-7
lines changed

5 files changed

+224
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2222

2323
- #762 - re-enabled `x86_64-unknown-dragonfly` target.
2424
- #747 - reduced android image sizes.
25+
- #746 - limit image permissions for android images.
2526
- #377 - update WINE versions to 7.0.
2627
- #734 - patch `arm-unknown-linux-gnueabihf` to build for ARMv6, and add architecture for crosstool-ng-based images.
2728
- #709 - Update Emscripten targets to `emcc` version 3.1.10

src/docker.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use std::io::Write;
12
use std::path::{Path, PathBuf};
23
use std::process::{Command, ExitStatus};
34
use std::{env, fs};
45

56
use crate::cargo::CargoMetadata;
67
use crate::extensions::{CommandExt, SafeCommand};
8+
use crate::file::write_file;
79
use crate::id;
810
use crate::{errors::*, file};
911
use crate::{Config, Target};
@@ -14,16 +16,23 @@ const DOCKER_IMAGES: &[&str] = &include!(concat!(env!("OUT_DIR"), "/docker-image
1416
const CROSS_IMAGE: &str = "ghcr.io/cross-rs";
1517
const DOCKER: &str = "docker";
1618
const PODMAN: &str = "podman";
19+
// secured profile based off the docker documentation for denied syscalls:
20+
// https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile
21+
// note that we've allow listed `clone` and `clone3`, which is necessary
22+
// to fork the process, and which podman allows by default.
23+
const SECCOMP: &str = include_str!("seccomp.json");
1724

1825
// determine if the container engine is docker. this fixes issues with
1926
// any aliases (#530), and doesn't fail if an executable suffix exists.
20-
fn get_is_docker(ce: std::path::PathBuf, verbose: bool) -> Result<bool> {
27+
fn get_engine_type(ce: std::path::PathBuf, verbose: bool) -> Result<(bool, bool)> {
2128
let stdout = Command::new(ce)
2229
.arg("--help")
2330
.run_and_get_stdout(verbose)?
2431
.to_lowercase();
2532

26-
Ok(stdout.contains("docker") && !stdout.contains("emulate"))
33+
let is_docker = stdout.contains("docker") && !stdout.contains("emulate");
34+
let is_podman = stdout.contains("podman");
35+
Ok((is_docker, is_podman))
2736
}
2837

2938
fn get_container_engine() -> Result<std::path::PathBuf, which::Error> {
@@ -172,7 +181,8 @@ pub fn run(
172181
let runner = config.runner(target)?;
173182

174183
let mut docker = docker_command("run")?;
175-
let is_docker = get_is_docker(get_container_engine().unwrap(), verbose)?;
184+
#[allow(unused_variables)] // is_podman, target_os = "windows"
185+
let (is_docker, is_podman) = get_engine_type(get_container_engine().unwrap(), verbose)?;
176186

177187
for ref var in config.env_passthrough(target)? {
178188
validate_env_var(var)?;
@@ -233,8 +243,33 @@ pub fn run(
233243

234244
docker.arg("--rm");
235245

236-
if target.needs_docker_privileged() {
237-
docker.arg("--privileged");
246+
// docker uses seccomp now on all installations
247+
if target.needs_docker_seccomp() {
248+
let seccomp = if is_docker && cfg!(target_os = "windows") {
249+
// docker on windows fails due to a bug in reading the profile
250+
// https://github.com/docker/for-win/issues/12760
251+
"unconfined".to_string()
252+
} else {
253+
#[allow(unused_mut)] // target_os = "windows"
254+
let mut path = env::current_dir()
255+
.wrap_err("couldn't get current directory")?
256+
.canonicalize()
257+
.wrap_err_with(|| "when canonicalizing current_dir".to_string())?
258+
.join("target")
259+
.join(target.triple())
260+
.join("seccomp.json");
261+
if !path.exists() {
262+
write_file(&path, false)?.write_all(SECCOMP.as_bytes())?;
263+
}
264+
#[cfg(target_os = "windows")]
265+
if is_podman {
266+
// podman weirdly expects a WSL path here, and fails otherwise
267+
path = wslpath(&path, verbose)?;
268+
}
269+
path.display().to_string()
270+
};
271+
272+
docker.args(&["--security-opt", &format!("seccomp={}", seccomp)]);
238273
}
239274

240275
// We need to specify the user for Docker, but not for Podman.

src/file.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fs::File;
1+
use std::fs::{self, File};
22
use std::io::Read;
33
use std::path::{Path, PathBuf};
44

@@ -35,3 +35,18 @@ fn _canonicalize(path: &Path) -> Result<PathBuf> {
3535
Path::new(&path).canonicalize().map_err(Into::into)
3636
}
3737
}
38+
39+
pub fn write_file(path: impl AsRef<Path>, overwrite: bool) -> Result<File> {
40+
let path = path.as_ref();
41+
fs::create_dir_all(
42+
&path.parent().ok_or_else(|| {
43+
eyre::eyre!("could not find parent directory for `{}`", path.display())
44+
})?,
45+
)
46+
.wrap_err_with(|| format!("couldn't create directory `{}`", path.display()))?;
47+
fs::OpenOptions::new()
48+
.write(true)
49+
.create_new(!overwrite)
50+
.open(&path)
51+
.wrap_err(format!("could't write to file `{}`", path.display()))
52+
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ impl Target {
211211
!native && (self.is_linux() || self.is_windows() || self.is_bare_metal())
212212
}
213213

214-
fn needs_docker_privileged(&self) -> bool {
214+
fn needs_docker_seccomp(&self) -> bool {
215215
let arch_32bit = self.triple().starts_with("arm")
216216
|| self.triple().starts_with("i586")
217217
|| self.triple().starts_with("i686");

src/seccomp.json

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
{
2+
"defaultAction": "SCMP_ACT_ALLOW",
3+
"syscalls": [
4+
{
5+
"names": [
6+
"add_key",
7+
"get_kernel_syms",
8+
"keyctl",
9+
"move_pages",
10+
"nfsservctl",
11+
"perf_event_open",
12+
"pivot_root",
13+
"query_module",
14+
"request_key",
15+
"sysfs",
16+
"_sysctl",
17+
"uselib",
18+
"userfaultfd",
19+
"ustat"
20+
],
21+
"action": "SCMP_ACT_ERRNO",
22+
"errnoRet": 1
23+
},
24+
{
25+
"names": [
26+
"acct"
27+
],
28+
"action": "SCMP_ACT_ERRNO",
29+
"errnoRet": 1,
30+
"excludes": {
31+
"caps": [
32+
"CAP_SYS_PACCT"
33+
]
34+
}
35+
},
36+
{
37+
"names": [
38+
"bpf",
39+
"lookup_dcookie",
40+
"mount",
41+
"quotactl",
42+
"quotactl_fd",
43+
"setns",
44+
"swapon",
45+
"swapoff",
46+
"umount",
47+
"umount2",
48+
"unshare",
49+
"vm86",
50+
"vm86old",
51+
"pciconfig_read",
52+
"pciconfig_write",
53+
"salinfo_log_open",
54+
"salinfo_event_open",
55+
"sys_cacheflush",
56+
"rtas"
57+
],
58+
"action": "SCMP_ACT_ERRNO",
59+
"errnoRet": 1,
60+
"excludes": {
61+
"caps": [
62+
"CAP_SYS_ADMIN"
63+
]
64+
}
65+
},
66+
{
67+
"names": [
68+
"clock_adjtime",
69+
"clock_settime",
70+
"settimeofday",
71+
"stime"
72+
],
73+
"action": "SCMP_ACT_ERRNO",
74+
"errnoRet": 1,
75+
"excludes": {
76+
"caps": [
77+
"CAP_SYS_TIME"
78+
]
79+
}
80+
},
81+
{
82+
"names": [
83+
"create_module",
84+
"delete_module",
85+
"finit_module",
86+
"init_module"
87+
],
88+
"action": "SCMP_ACT_ERRNO",
89+
"errnoRet": 1,
90+
"excludes": {
91+
"caps": [
92+
"CAP_SYS_MODULE"
93+
]
94+
}
95+
},
96+
{
97+
"names": [
98+
"get_mempolicy",
99+
"mbind",
100+
"set_mempolicy"
101+
],
102+
"action": "SCMP_ACT_ERRNO",
103+
"errnoRet": 1,
104+
"excludes": {
105+
"caps": [
106+
"CAP_SYS_NICE"
107+
]
108+
}
109+
},
110+
{
111+
"names": [
112+
"ioperm",
113+
"iopl"
114+
],
115+
"action": "SCMP_ACT_ERRNO",
116+
"errnoRet": 1,
117+
"excludes": {
118+
"caps": [
119+
"CAP_SYS_RAWIO"
120+
]
121+
}
122+
},
123+
{
124+
"names": [
125+
"kcmp",
126+
"process_vm_readv",
127+
"process_vm_writev",
128+
"ptrace"
129+
],
130+
"action": "SCMP_ACT_ERRNO",
131+
"errnoRet": 1,
132+
"excludes": {
133+
"caps": [
134+
"CAP_SYS_PTRACE"
135+
]
136+
}
137+
},
138+
{
139+
"names": [
140+
"kexec_file_load",
141+
"kexec_load",
142+
"reboot"
143+
],
144+
"action": "SCMP_ACT_ERRNO",
145+
"errnoRet": 1,
146+
"excludes": {
147+
"caps": [
148+
"CAP_SYS_BOOT"
149+
]
150+
}
151+
},
152+
{
153+
"names": [
154+
"name_to_handle_at",
155+
"open_by_handle_at"
156+
],
157+
"action": "SCMP_ACT_ERRNO",
158+
"errnoRet": 1,
159+
"excludes": {
160+
"caps": [
161+
"CAP_DAC_READ_SEARCH"
162+
]
163+
}
164+
}
165+
]
166+
}

0 commit comments

Comments
 (0)