5 #include <stdnoreturn.h>
12 #include <linux/capability.h>
13 #include <sys/prctl.h>
19 #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
21 extern char **environ
;
23 // The WRAPPER_DIR macro is supplied at compile time so that it cannot
24 // be changed at runtime
25 static char *wrapper_dir
= WRAPPER_DIR
;
27 // Wrapper debug variable name
28 static char *wrapper_debug
= "WRAPPER_DEBUG";
32 #if __BYTE_ORDER == __BIG_ENDIAN
33 #define LE32_TO_H(x) bswap_32(x)
35 #define LE32_TO_H(x) (x)
38 static noreturn
void assert_failure(const char *assertion
) {
39 fprintf(stderr
, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion
);
44 int get_last_cap(unsigned *last_cap
) {
45 FILE* file
= fopen("/proc/sys/kernel/cap_last_cap", "r");
47 int saved_errno
= errno
;
48 fprintf(stderr
, "failed to open /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno
));
51 int res
= fscanf(file
, "%u", last_cap
);
53 int saved_errno
= errno
;
54 fprintf(stderr
, "could not read number from /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno
));
61 // Given the path to this program, fetch its configured capability set
62 // (as set by `setcap ... /path/to/file`) and raise those capabilities
63 // into the Ambient set.
64 static int make_caps_ambient(const char *self_path
) {
65 struct vfs_ns_cap_data data
= {};
66 int r
= getxattr(self_path
, "security.capability", &data
, sizeof(data
));
69 if (errno
== ENODATA
) {
70 // no capabilities set
73 fprintf(stderr
, "cannot get capabilities for %s: %s", self_path
, strerror(errno
));
78 uint32_t version
= LE32_TO_H(data
.magic_etc
) & VFS_CAP_REVISION_MASK
;
80 case VFS_CAP_REVISION_1
:
83 case VFS_CAP_REVISION_2
:
84 case VFS_CAP_REVISION_3
:
88 fprintf(stderr
, "BUG! Unsupported capability version 0x%x on %s. Report to NixOS bugtracker\n", version
, self_path
);
92 const struct __user_cap_header_struct header
= {
93 .version
= _LINUX_CAPABILITY_VERSION_3
,
96 struct __user_cap_data_struct user_data
[2] = {};
98 for (size_t i
= 0; i
< size
; i
++) {
99 // merge inheritable & permitted into one
100 user_data
[i
].permitted
= user_data
[i
].inheritable
=
101 LE32_TO_H(data
.data
[i
].inheritable
) | LE32_TO_H(data
.data
[i
].permitted
);
104 if (syscall(SYS_capset
, &header
, &user_data
) < 0) {
105 fprintf(stderr
, "failed to inherit capabilities: %s", strerror(errno
));
109 r
= get_last_cap(&last_cap
);
113 uint64_t set
= user_data
[0].permitted
| (uint64_t)user_data
[1].permitted
<< 32;
114 for (unsigned cap
= 0; cap
< last_cap
; cap
++) {
115 if (!(set
& (1ULL << cap
))) {
119 // Check for the cap_setpcap capability, we set this on the
120 // wrapper so it can elevate the capabilities to the Ambient
121 // set but we do not want to propagate it down into the
124 // TODO: what happens if that's the behavior you want
125 // though???? I'm preferring a strict vs. loose policy here.
126 if (cap
== CAP_SETPCAP
) {
127 if(getenv(wrapper_debug
)) {
128 fprintf(stderr
, "cap_setpcap in set, skipping it\n");
132 if (prctl(PR_CAP_AMBIENT
, PR_CAP_AMBIENT_RAISE
, (unsigned long) cap
, 0, 0)) {
133 fprintf(stderr
, "cannot raise the capability %d into the ambient set: %s\n", cap
, strerror(errno
));
136 if (getenv(wrapper_debug
)) {
137 fprintf(stderr
, "raised %d into the ambient capability set\n", cap
);
144 int readlink_malloc(const char *p
, char **ret
) {
145 size_t l
= FILENAME_MAX
+1;
149 char *c
= calloc(l
, sizeof(char));
154 ssize_t n
= readlink(p
, c
, l
-1);
161 if ((size_t) n
< l
-1) {
172 int main(int argc
, char **argv
) {
174 char *self_path
= NULL
;
175 int self_path_size
= readlink_malloc("/proc/self/exe", &self_path
);
176 if (self_path_size
< 0) {
177 fprintf(stderr
, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size
));
180 // Make sure that we are being executed from the right location,
181 // i.e., `safe_wrapper_dir'. This is to prevent someone from creating
182 // hard link `X' from some other location, along with a false
183 // `X.real' file, to allow arbitrary programs from being executed
184 // with elevated capabilities.
185 int len
= strlen(wrapper_dir
);
186 if (len
> 0 && '/' == wrapper_dir
[len
- 1])
188 ASSERT(!strncmp(self_path
, wrapper_dir
, len
));
189 ASSERT('/' == wrapper_dir
[0]);
190 ASSERT('/' == self_path
[len
]);
192 // Make *really* *really* sure that we were executed as
193 // `self_path', and not, say, as some other setuid program. That
194 // is, our effective uid/gid should match the uid/gid of
197 ASSERT(lstat(self_path
, &st
) != -1);
199 ASSERT(!(st
.st_mode
& S_ISUID
) || (st
.st_uid
== geteuid()));
200 ASSERT(!(st
.st_mode
& S_ISGID
) || (st
.st_gid
== getegid()));
202 // And, of course, we shouldn't be writable.
203 ASSERT(!(st
.st_mode
& (S_IWGRP
| S_IWOTH
)));
205 // Read the path of the real (wrapped) program from <self>.real.
206 char real_fn
[PATH_MAX
+ 10];
207 int real_fn_size
= snprintf(real_fn
, sizeof(real_fn
), "%s.real", self_path
);
208 ASSERT(real_fn_size
< sizeof(real_fn
));
210 int fd_self
= open(real_fn
, O_RDONLY
);
211 ASSERT(fd_self
!= -1);
213 char source_prog
[PATH_MAX
];
214 len
= read(fd_self
, source_prog
, PATH_MAX
);
216 ASSERT(len
< sizeof(source_prog
));
218 source_prog
[len
] = 0;
222 // Read the capabilities set on the wrapper and raise them in to
223 // the ambient set so the program we're wrapping receives the
225 if (make_caps_ambient(self_path
) != 0) {
231 execve(source_prog
, argv
, environ
);
233 fprintf(stderr
, "%s: cannot run `%s': %s\n",
234 argv
[0], source_prog
, strerror(errno
));