Skip to content

Commit 671be21

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 6a7fa09 commit 671be21

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
@@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2020
### Changed
2121

2222
- #747 - reduced android image sizes.
23+
- #746 - limit image permissions for android images.
2324
- #377 - update WINE versions to 7.0.
2425
- #734 - patch `arm-unknown-linux-gnueabihf` to build for ARMv6, and add architecture for crosstool-ng-based images.
2526
- #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,10 +1,12 @@
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::Root;
67
use crate::errors::*;
78
use crate::extensions::{CommandExt, SafeCommand};
9+
use crate::file::write_file;
810
use crate::id;
911
use crate::{Config, Target};
1012
use atty::Stream;
@@ -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> {
@@ -137,7 +146,8 @@ pub fn run(
137146
let runner = config.runner(target)?;
138147

139148
let mut docker = docker_command("run")?;
140-
let is_docker = get_is_docker(get_container_engine().unwrap(), verbose)?;
149+
#[allow(unused_variables)] // is_podman, target_os = "windows"
150+
let (is_docker, is_podman) = get_engine_type(get_container_engine().unwrap(), verbose)?;
141151

142152
for ref var in config.env_passthrough(target)? {
143153
validate_env_var(var)?;
@@ -186,8 +196,33 @@ pub fn run(
186196

187197
docker.arg("--rm");
188198

189-
if target.needs_docker_privileged() {
190-
docker.arg("--privileged");
199+
// docker uses seccomp now on all installations
200+
if target.needs_docker_seccomp() {
201+
let seccomp = if is_docker && cfg!(target_os = "windows") {
202+
// docker on windows fails due to a bug in reading the profile
203+
// https://github.com/docker/for-win/issues/12760
204+
"unconfined".to_string()
205+
} else {
206+
#[allow(unused_mut)] // target_os = "windows"
207+
let mut path = env::current_dir()
208+
.wrap_err("couldn't get current directory")?
209+
.canonicalize()
210+
.wrap_err_with(|| "when canonicalizing current_dir".to_string())?
211+
.join("target")
212+
.join(target.triple())
213+
.join("seccomp.json");
214+
if !path.exists() {
215+
write_file(&path, false)?.write_all(SECCOMP.as_bytes())?;
216+
}
217+
#[cfg(target_os = "windows")]
218+
if is_podman {
219+
// podman weirdly expects a WSL path here, and fails otherwise
220+
path = wslpath(&path, verbose)?;
221+
}
222+
path.display().to_string()
223+
};
224+
225+
docker.args(&["--security-opt", &format!("seccomp={}", seccomp)]);
191226
}
192227

193228
// 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;
44

@@ -19,3 +19,18 @@ fn read_(path: &Path) -> Result<String> {
1919
.wrap_err_with(|| format!("couldn't read {}", path.display()))?;
2020
Ok(s)
2121
}
22+
23+
pub fn write_file(path: impl AsRef<Path>, overwrite: bool) -> Result<File> {
24+
let path = path.as_ref();
25+
fs::create_dir_all(
26+
&path.parent().ok_or_else(|| {
27+
eyre::eyre!("could not find parent directory for `{}`", path.display())
28+
})?,
29+
)
30+
.wrap_err_with(|| format!("couldn't create directory `{}`", path.display()))?;
31+
fs::OpenOptions::new()
32+
.write(true)
33+
.create_new(!overwrite)
34+
.open(&path)
35+
.wrap_err(format!("could't write to file `{}`", path.display()))
36+
}

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)