Skip to content

Commit 82b9504

Browse files
committed
sec,mknod: enforce that caller has CAP_MKNOD
While it wouldn't matter much if a user in the targer user namespace does not have CAP_MKNOD when creating safe devices, it ultimately is a breach in documented behavior that can subtly change existing programs relying on that behavior. One example is checking for EPERM and falling back to different logic like bind-mounting, which some programs that manipulate namespaces (including bst) do.
1 parent 31fbebe commit 82b9504

File tree

3 files changed

+51
-3
lines changed

3 files changed

+51
-3
lines changed

capable.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,28 @@ static int bst_capget(cap_user_header_t header, cap_user_data_t data)
3535
return (int) syscall(SYS_capget, header, data);
3636
}
3737

38+
int tid_capget(pid_t tid, struct capabilities *caps)
39+
{
40+
struct __user_cap_header_struct hdr = {
41+
.version = _LINUX_CAPABILITY_VERSION_3,
42+
.pid = tid,
43+
};
44+
45+
struct __user_cap_data_struct current[2];
46+
47+
if (bst_capget(&hdr, current) == -1) {
48+
return -1;
49+
}
50+
51+
*caps = (struct capabilities) {
52+
.inheritable = (uint64_t) current[1].inheritable << 32 | current[0].inheritable,
53+
.permitted = (uint64_t) current[1].permitted << 32 | current[0].permitted,
54+
.effective = (uint64_t) current[1].effective << 32 | current[0].effective,
55+
};
56+
57+
return 0;
58+
}
59+
3860
void init_capabilities(void)
3961
{
4062
if (bst_capget(&hdr, current) == -1) {

capable.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# include <stdbool.h>
1111
# include <stdint.h>
1212
# include <linux/capability.h>
13+
# include <unistd.h>
1314

1415
/* Define more useful capability constants */
1516
# define BST_CAP_SYS_ADMIN ((uint64_t) 1 << CAP_SYS_ADMIN)
@@ -30,6 +31,14 @@ void make_capable(uint64_t cap);
3031
void reset_capabilities(void);
3132
void drop_capabilities(void);
3233

34+
struct capabilities {
35+
uint64_t inheritable;
36+
uint64_t permitted;
37+
uint64_t effective;
38+
};
39+
40+
int tid_capget(pid_t tid, struct capabilities *capabilities);
41+
3342
# define with_capable(capmask) \
3443
for (int __ok = ((void) make_capable(capmask), 1); __ok; (void) reset_capabilities(), __ok = 0)
3544

sec.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ static int run_in_process_context(int seccomp_fd, int procfd,
113113
}
114114

115115
if (memfd == -1) {
116-
warn("open /proc/<pid>/mem");
116+
warn("open /proc/%d/mem", req->pid);
117117
rc = -EINVAL;
118118
goto error_close;
119119
}
120120

121121
if (mntns == -1) {
122-
warn("open /proc/<pid>/ns/mnt");
122+
warn("open /proc/%d/ns/mnt", req->pid);
123123
rc = -EINVAL;
124124
goto error_close;
125125
}
@@ -199,30 +199,46 @@ static int run_in_process_context(int seccomp_fd, int procfd,
199199
}
200200

201201
struct mknodat_args {
202+
pid_t pid;
202203
int dirfd;
203204
mode_t mode;
204205
dev_t dev;
205206
char pathname[PATH_MAX];
206207

207208
struct proc_status status;
209+
struct capabilities caps;
208210
};
209211

210212
static int sec__mknodat_init(int procfd, void *cookie)
211213
{
212214
struct mknodat_args *args = cookie;
213215

214216
if (proc_read_status(procfd, &args->status) == -1) {
215-
warn("proc_read_status /proc/<pid>/status");
217+
warn("proc_read_status /proc/%d/status", args->pid);
216218
return -EINVAL;
217219
}
218220

221+
if (tid_capget(args->pid, &args->caps) == -1) {
222+
int err = errno;
223+
warn("capget %d", args->pid);
224+
return -err;
225+
}
226+
219227
return 0;
220228
}
221229

222230
static int sec__mknodat_callback(int procfd, void *cookie)
223231
{
224232
struct mknodat_args *args = cookie;
225233

234+
/* It wouldn't technically matter for a user without CAP_MKNOD to be
235+
able to create safe devices, but some programs handle EPERM with
236+
fallback code to account for root and non-root execution. Deny
237+
the syscall in order to be faithful to that behavior */
238+
if (!(args->caps.effective & BST_CAP_MKNOD)) {
239+
return -EPERM;
240+
}
241+
226242
mode_t old_umask = umask(args->status.umask);
227243

228244
uid_t uid = geteuid();
@@ -297,6 +313,7 @@ safe: {}
297313
}
298314

299315
struct mknodat_args args = {
316+
.pid = req->pid,
300317
.dirfd = realdirfd,
301318
.dev = dev,
302319
.mode = mode,

0 commit comments

Comments
 (0)