Skip to content

Commit a4ae516

Browse files
committed
mkcomposefs: add --sandbox flag
This commit adds a new --sandbox flag to mkcomposefs, enabling an attempt to isolate the process within a restricted environment. The sandbox limits the process's access to host resources, reducing potential attack surfaces. It is a best-effort attempt and does not guarantee full isolation. Signed-off-by: Giuseppe Scrivano <[email protected]>
1 parent 5ed1fb6 commit a4ae516

7 files changed

+264
-4
lines changed

ci/Containerfile.c9s-bootc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM quay.io/centos/centos:stream9 as build
22
RUN dnf -y install dnf-utils zstd && dnf config-manager --enable crb && dnf builddep -y composefs && \
3-
dnf -y install meson
3+
dnf -y install meson libseccomp-devel libcap-devel
44
COPY . /build
55
WORKDIR /build
66
RUN set -x; ls -al; meson setup target --prefix=/usr && meson compile -C target && \

hacking/installdeps.sh

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ PACKAGES=" \
1717
python3 \
1818
libcap2-bin \
1919
meson \
20+
libseccomp-dev \
21+
libcap-dev \
2022
"
2123

2224
# Split required and optional packages based on input variable ALLOW_MISSING:

man/mkcomposefs.md

+5
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ will be a mountable composefs image.
7474
: Number of threads to be used to calculate the file digests and copy.
7575
Default thread count is the number of processors when *--threads* is not specified.
7676

77+
**\-\-sandbox**
78+
It runs the current process in a sandboxed environment using different Linux kernel
79+
features. It is a best effort attempt to limit what the process can access while it
80+
is processing an untrusted input.
81+
7782
# FORMAT VERSIONING
7883

7984
Composefs images are binary reproduceable, meaning that for a given

tools/meson.build

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
libcomposefs_dep = declare_dependency(link_with : libcomposefs, include_directories : config_inc)
22

3+
seccomp_dep = dependency('libseccomp', required : true)
4+
5+
cap_dep = dependency('libcap', required : true)
6+
37
thread_dep = dependency('threads')
48

59
executable('mkcomposefs',
6-
'mkcomposefs.c',
7-
dependencies : [libcomposefs_dep, thread_dep],
10+
['mkcomposefs.c', 'mkcomposefs-sandbox.c'],
11+
dependencies : [libcomposefs_dep, thread_dep, seccomp_dep, cap_dep],
812
install : true,
913
)
1014

tools/mkcomposefs-sandbox.c

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/* lcfs
2+
Copyright (C) 2021-2024 Giuseppe Scrivano <[email protected]>
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
#define _GNU_SOURCE
19+
20+
#include "config.h"
21+
22+
#include "mkcomposefs-sandbox.h"
23+
24+
#include <stdlib.h>
25+
#include <stdio.h>
26+
#include <linux/limits.h>
27+
#include <string.h>
28+
#include <err.h>
29+
#include <errno.h>
30+
#include <unistd.h>
31+
#include <fcntl.h>
32+
#include <sys/types.h>
33+
#include <sys/sysmacros.h>
34+
#include <getopt.h>
35+
#include <sys/prctl.h>
36+
#include <sched.h>
37+
#include <sys/mount.h>
38+
#include <sys/capability.h>
39+
#include <linux/seccomp.h>
40+
#include <seccomp.h>
41+
#include <time.h>
42+
43+
static void do_seccomp_sandbox(void)
44+
{
45+
scmp_filter_ctx ctx;
46+
int ret;
47+
size_t i;
48+
int syscalls[] = {
49+
SCMP_SYS(brk), SCMP_SYS(close), SCMP_SYS(exit),
50+
SCMP_SYS(exit_group), SCMP_SYS(fstat), SCMP_SYS(lseek),
51+
SCMP_SYS(mmap), SCMP_SYS(mremap), SCMP_SYS(munmap),
52+
SCMP_SYS(newfstatat), SCMP_SYS(read), SCMP_SYS(readv),
53+
SCMP_SYS(sysinfo), SCMP_SYS(write), SCMP_SYS(writev),
54+
};
55+
56+
/* Use ENOSYS by default so that libraries can attempt a fallback syscall instead of failing immediately. */
57+
ctx = seccomp_init(SCMP_ACT_ERRNO(ENOSYS));
58+
if (ctx == NULL)
59+
err(EXIT_FAILURE, "seccomp_init");
60+
61+
for (i = 0; i < sizeof(syscalls) / sizeof(syscalls[0]); i++) {
62+
ret = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls[i], 0);
63+
if (ret < 0) {
64+
errno = -ret;
65+
err(EXIT_FAILURE, "seccomp_rule_add");
66+
}
67+
}
68+
69+
ret = seccomp_load(ctx);
70+
if (ret < 0) {
71+
errno = -ret;
72+
err(EXIT_FAILURE, "seccomp_load");
73+
}
74+
}
75+
76+
static int pivot_root(const char *new_root, const char *put_old)
77+
{
78+
return syscall(__NR_pivot_root, new_root, put_old);
79+
}
80+
81+
static void do_namespace_sandbox(void)
82+
{
83+
uid_t uid = geteuid();
84+
gid_t gid = getegid();
85+
int ret, fd;
86+
int old_root;
87+
char *cwd;
88+
89+
ret = unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS |
90+
CLONE_NEWIPC | CLONE_NEWNET);
91+
if (ret < 0)
92+
return;
93+
94+
fd = open("/proc/self/setgroups", O_WRONLY | O_CLOEXEC);
95+
if (fd < 0)
96+
err(EXIT_FAILURE, "open /proc/self/setgroups");
97+
ret = write(fd, "deny", 4);
98+
if (ret < 0)
99+
err(EXIT_FAILURE, "write to /proc/self/gid_map");
100+
close(fd);
101+
102+
fd = open("/proc/self/gid_map", O_WRONLY | O_CLOEXEC);
103+
if (fd < 0)
104+
err(EXIT_FAILURE, "open /proc/self/gid_map");
105+
ret = dprintf(fd, "0 %d 1\n", gid);
106+
if (ret < 0)
107+
err(EXIT_FAILURE, "write to /proc/self/gid_map");
108+
close(fd);
109+
110+
fd = open("/proc/self/uid_map", O_WRONLY | O_CLOEXEC);
111+
if (fd < 0)
112+
err(EXIT_FAILURE, "open /proc/self/uid_map");
113+
ret = dprintf(fd, "0 %d 1\n", uid);
114+
if (ret < 0)
115+
err(EXIT_FAILURE, "write to /proc/self/uid_map");
116+
close(fd);
117+
118+
ret = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);
119+
if (ret < 0)
120+
err(EXIT_FAILURE, "mount /");
121+
122+
cwd = get_current_dir_name();
123+
if (!cwd)
124+
err(EXIT_FAILURE, "get_current_dir_name");
125+
126+
ret = mount(NULL, cwd, "tmpfs", 0, NULL);
127+
if (ret < 0)
128+
err(EXIT_FAILURE, "mount tmpfs");
129+
130+
old_root = open("/", O_PATH | O_DIRECTORY | O_CLOEXEC);
131+
if (old_root < 0)
132+
err(EXIT_FAILURE, "open /");
133+
134+
ret = chdir(cwd);
135+
if (ret < 0)
136+
err(EXIT_FAILURE, "chdir cwd");
137+
138+
free(cwd);
139+
cwd = NULL;
140+
141+
ret = pivot_root(".", ".");
142+
if (ret < 0)
143+
err(EXIT_FAILURE, "pivot_root");
144+
145+
ret = fchdir(old_root);
146+
if (ret < 0)
147+
err(EXIT_FAILURE, "fchdir");
148+
close(old_root);
149+
150+
ret = umount2(".", MNT_DETACH);
151+
if (ret < 0)
152+
err(EXIT_FAILURE, "umount2");
153+
154+
ret = chdir("/");
155+
if (ret < 0)
156+
err(EXIT_FAILURE, "fchdir");
157+
}
158+
159+
static void drop_caps(void)
160+
{
161+
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
162+
struct __user_cap_data_struct data[2] = { { 0 } };
163+
int ret, cap;
164+
ret = prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0);
165+
if (ret < 0)
166+
err(EXIT_FAILURE, "prctl(PR_SET_KEEPCAPS)");
167+
168+
for (cap = 0;; cap++) {
169+
ret = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0);
170+
if (ret < 0 && errno != EINVAL)
171+
err(EXIT_FAILURE, "prctl(PR_CAPBSET_DROP)");
172+
if (ret < 0)
173+
break;
174+
}
175+
176+
ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
177+
if (ret < 0 && errno != EINVAL)
178+
err(EXIT_FAILURE, "prctl(PR_CAP_AMBIENT)");
179+
180+
ret = capset(&hdr, data);
181+
if (ret < 0 && errno != EINVAL)
182+
err(EXIT_FAILURE, "capset");
183+
}
184+
185+
static void do_set_oom_score_adj(void)
186+
{
187+
int fd, ret;
188+
189+
fd = open("/proc/self/oom_score_adj", O_WRONLY);
190+
if (fd < 0)
191+
err(EXIT_FAILURE, "open /proc/self/oom_score_adj");
192+
193+
ret = write(fd, "1000", 4);
194+
if (ret < 0)
195+
err(EXIT_FAILURE, "write to /proc/self/oom_score_adj");
196+
197+
close(fd);
198+
}
199+
200+
void create_sandbox(void)
201+
{
202+
do_set_oom_score_adj();
203+
do_namespace_sandbox();
204+
drop_caps();
205+
206+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
207+
err(EXIT_FAILURE, "prctl(PR_SET_NO_NEW_PRIVS)");
208+
209+
do_seccomp_sandbox();
210+
}

tools/mkcomposefs-sandbox.h

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* lcfs
2+
Copyright (C) 2021-2024 Giuseppe Scrivano <[email protected]>
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
void create_sandbox(void);

tools/mkcomposefs.c

+22-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "libcomposefs/lcfs-utils.h"
2424
#include "libcomposefs/lcfs-internal.h"
2525

26+
#include "mkcomposefs-sandbox.h"
27+
2628
#include <stdio.h>
2729
#include <linux/limits.h>
2830
#include <string.h>
@@ -70,6 +72,7 @@ static __attribute__((format(printf, 1, 2))) char *make_error(const char *fmt, .
7072
#define OPT_MIN_VERSION 114
7173
#define OPT_THREADS 115
7274
#define OPT_MAX_VERSION 116
75+
#define OPT_SANDBOX 117
7376

7477
static size_t split_at(const char **start, size_t *length, char split_char,
7578
bool *partial)
@@ -1467,7 +1470,8 @@ static void usage(const char *argv0)
14671470
" --from-file The source is a dump file, not a directory\n"
14681471
" --min-version=N Use this minimal format version (default=%d)\n"
14691472
" --max-version=N Use this maxium format version (default=%d)\n"
1470-
" --threads=N Use this to override the default number of threads used to calculate digest and copy files (default=%d)\n",
1473+
" --threads=N Use this to override the default number of threads used to calculate digest and copy files (default=%d)\n"
1474+
" --sandbox Sandbox the process before processing the input file\n",
14711475
bin, LCFS_DEFAULT_VERSION_MIN, LCFS_DEFAULT_VERSION_MAX,
14721476
get_cpu_count());
14731477
}
@@ -1541,6 +1545,12 @@ int main(int argc, char **argv)
15411545
flag: NULL,
15421546
val: OPT_THREADS
15431547
},
1548+
{
1549+
name: "sandbox",
1550+
has_arg: no_argument,
1551+
flag: NULL,
1552+
val: OPT_SANDBOX
1553+
},
15441554
{},
15451555
};
15461556
struct lcfs_write_options_s options = { 0 };
@@ -1559,6 +1569,7 @@ int main(int argc, char **argv)
15591569
FILE *out_file;
15601570
char *failed_path;
15611571
bool version_set = false;
1572+
bool use_sandbox = false;
15621573
long min_version = 0;
15631574
long max_version = 0;
15641575
char *end;
@@ -1624,6 +1635,9 @@ int main(int argc, char **argv)
16241635
exit(EXIT_FAILURE);
16251636
}
16261637
break;
1638+
case OPT_SANDBOX:
1639+
use_sandbox = true;
1640+
break;
16271641
case ':':
16281642
fprintf(stderr, "option needs a value\n");
16291643
exit(EXIT_FAILURE);
@@ -1698,6 +1712,9 @@ int main(int argc, char **argv)
16981712
close_input = true;
16991713
}
17001714

1715+
if (use_sandbox)
1716+
create_sandbox();
1717+
17011718
char *err = NULL;
17021719
root = tree_from_dump(input, &err);
17031720
if (root == NULL) {
@@ -1716,6 +1733,10 @@ int main(int argc, char **argv)
17161733
buildflag_copy &= ~LCFS_BUILD_BY_DIGEST;
17171734
buildflag_copy |= LCFS_BUILD_NO_INLINE;
17181735

1736+
if (use_sandbox)
1737+
err(EXIT_FAILURE,
1738+
"the sandbox option is supported only with --from-file");
1739+
17191740
root = lcfs_build(AT_FDCWD, src_path, buildflag_copy, &failed_path);
17201741
if (root == NULL)
17211742
err(EXIT_FAILURE, "error accessing %s", failed_path);

0 commit comments

Comments
 (0)