this repo has no description
1#define _GNU_SOURCE
2#define _FILE_OFFSET_BITS 64
3#include <linux/sched.h>
4
5#include <sys/stat.h>
6#include <sys/types.h>
7#include <sys/eventfd.h>
8#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24
9#include <sys/random.h>
10#endif
11#include <sys/syscall.h>
12#include <sys/wait.h>
13#include <sys/mount.h>
14#include <limits.h>
15#include <errno.h>
16#include <dirent.h>
17#include <fcntl.h>
18#include <signal.h>
19#include <unistd.h>
20#include <string.h>
21
22
23#define CAML_INTERNALS
24#include <caml/mlvalues.h>
25#include <caml/unixsupport.h>
26#include <caml/memory.h>
27#include <caml/custom.h>
28#include <caml/signals.h>
29#include <caml/fail.h>
30#undef CAML_INTERNALS
31
32// From Eio
33#include <include/fork_action.h>
34
35#ifndef SYS_pidfd_send_signal
36#define SYS_pidfd_send_signal 424
37#endif
38
39// struct clone_args isn't defined in linux-lts headers, so define it here
40// Note that this struct is versioned by size. See linux/sched.h for details
41struct caml_void_clone_args
42{
43 uint64_t flags;
44 uint64_t pidfd;
45 uint64_t child_tid;
46 uint64_t parent_tid;
47 uint64_t exit_signal;
48 uint64_t stack;
49 uint64_t stack_size;
50 uint64_t tls;
51};
52
53static int
54pidfd_send_signal (int pidfd, int sig, siginfo_t *info, unsigned int flags)
55{
56 return syscall (SYS_pidfd_send_signal, pidfd, sig, info, flags);
57}
58
59CAMLprim value
60caml_void_pidfd_send_signal (value v_pidfd, value v_signal)
61{
62 CAMLparam0 ();
63 int res;
64
65 res =
66 pidfd_send_signal (Int_val (v_pidfd),
67 caml_convert_signal_number (Int_val (v_signal)), NULL,
68 0);
69 if (res == -1)
70 uerror ("pidfd_send_signal", Nothing);
71 CAMLreturn (Val_unit);
72}
73
74static pid_t
75clone3_no_fallback (struct caml_void_clone_args *cl_args)
76{
77 int *pidfd = (int *) (uintptr_t) cl_args->pidfd;
78 pid_t child_pid =
79 syscall (SYS_clone3, cl_args, sizeof (struct caml_void_clone_args));
80
81 if (child_pid >= 0)
82 return child_pid; /* Success! */
83
84 if (errno != ENOSYS && errno != EPERM)
85 {
86 uerror ("clone3", Nothing); /* Unknown error */
87 }
88
89 uerror ("clone3", Nothing);
90}
91
92CAMLprim value
93caml_void_clone3 (value v_errors, value v_flags, value v_actions)
94{
95 CAMLparam1 (v_actions);
96 CAMLlocal1 (v_result);
97 pid_t child_pid;
98 int pidfd = -1; /* Is automatically close-on-exec */
99
100 struct caml_void_clone_args cl_args = {
101 .flags = Int_val (v_flags),
102 .pidfd = (uintptr_t) & pidfd,
103 .exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */
104 .stack = (uintptr_t) NULL, /* Use copy-on-write parent stack */
105 .stack_size = 0,
106 };
107
108 child_pid = clone3_no_fallback (&cl_args);
109 if (child_pid == 0)
110 {
111 /* Run child actions (doesn't return) */
112 eio_unix_run_fork_actions (Int_val (v_errors), v_actions);
113 }
114
115 v_result = caml_alloc_tuple (2);
116 Store_field (v_result, 0, Val_long (child_pid));
117 Store_field (v_result, 1, Val_int (pidfd));
118
119 CAMLreturn (v_result);
120}
121
122
123// Actions
124
125// MOUNT/UNMOUNT
126static void
127action_mount (int errors, value v_config)
128{
129 value v_src = Field (v_config, 1);
130 value v_tgt = Field (v_config, 2);
131 value v_type = Field (v_config, 3);
132 value v_flags = Field (v_config, 4);
133
134 int r;
135
136 r =
137 mount (String_val (v_src), String_val (v_tgt), String_val (v_type),
138 Int_val (v_flags), NULL);
139
140 if (r != 0)
141 {
142 eio_unix_fork_error (errors, "mount", strerror (errno));
143 _exit (1);
144 }
145}
146
147CAMLprim value
148void_fork_mount (value v_unit)
149{
150 return Val_fork_fn (action_mount);
151}
152
153// Writes a single line to a file
154static int
155put_line (const char *filename, const char *line)
156{
157 int fd;
158 int written;
159
160 fd = open (filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644);
161
162 if (fd < 0)
163 {
164 return fd;
165 }
166
167 written = write (fd, line, strlen (line));
168
169 close (fd);
170
171 if (written != strlen (line))
172 {
173 return -1;
174 }
175
176 return 0;
177}
178
179// MAP UID/GID to root
180static void
181action_map_uid_gid (int errors, value v_config)
182{
183 value v_uid = Field (v_config, 1);
184 value v_gid = Field (v_config, 2);
185 int result;
186 char uid_line[30];
187 char gid_line[30];
188
189 // We map root onto the calling UID
190 snprintf (uid_line, sizeof (uid_line), "0 %i 1\n", Int_val (v_uid));
191 result = put_line ("/proc/self/uid_map", uid_line);
192
193 if (result < 0)
194 {
195 eio_unix_fork_error (errors, "map_uid_gid-uid", strerror (errno));
196 _exit (1);
197 }
198
199 /* From user_namespaces(7)
200 *
201 * Writing "deny" to the /proc/pid/setgroups file before writing to
202 * /proc/pid/gid_map will permanently disable setgroups(2) in a user
203 * namespace and allow writing to /proc/pid/gid_map without having
204 * the CAP_SETGID capability in the parent user namespace.
205 *
206 * See also: https://lwn.net/Articles/626665/ */
207
208 put_line ("/proc/self/setgroups", "deny\n");
209
210 if (result < 0)
211 {
212 eio_unix_fork_error (errors, "map_uid_gid-setgroups", strerror (errno));
213 _exit (1);
214 }
215
216 result =
217 snprintf (gid_line, sizeof (gid_line), "0 %i 1\n", Int_val (v_gid));
218 put_line ("/proc/self/gid_map", gid_line);
219
220 if (result < 0)
221 {
222 eio_unix_fork_error (errors, "map_uid_gid-gid", strerror (errno));
223 _exit (1);
224 }
225}
226
227
228CAMLprim value
229void_fork_map_uid_gid (value v_unit)
230{
231 return Val_fork_fn (action_map_uid_gid);
232}
233
234static void
235action_setuid (int errors, value v_config)
236{
237 value v_uid = Field(v_config, 1);
238
239 // if (setuid(1000)) {
240 // eio_unix_fork_error (errors, "setuid", strerror (errno));
241 // _exit (1);
242 // }
243}
244
245// SETUID
246CAMLprim value
247void_fork_setuid (value v_unit)
248{
249 return Val_fork_fn (action_setuid);
250}
251
252// PIVOT ROOT
253//
254static int
255pivot_root (const char *new_root, const char *put_old)
256{
257 return syscall (SYS_pivot_root, new_root, put_old);
258}
259
260// Is there too much OCaml stuff going on here for a fork_action ?
261static void
262action_pivot_root (int errors, value v_config)
263{
264 value v_new_root = Field (v_config, 1);
265 value v_root_flags = Field (v_config, 2);
266 value v_no_root = Field (v_config, 3);
267 value v_mounts = Field (v_config, 4);
268 char path[PATH_MAX];
269 char old_root_path[PATH_MAX];
270 char *new_root = String_val (v_new_root);
271 const char *put_old = ".old_root";
272
273 // From pivot_root example: We want to change the propagation type
274 // of root to be private so we can pivot it.
275 if (mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1)
276 {
277 eio_unix_fork_error (errors, "pivot_root-private", strerror (errno));
278 _exit (1);
279 }
280
281 // If no pivot_root was given, then we tmpfs the tmpdir we assume was passed.
282 if (Bool_val (v_no_root))
283 {
284 // Make a temporary directory... can't because it allocates ?
285 //if (mkdtemp(new_root) != NULL) {
286 // eio_unix_fork_error(errors, new_root, strerror(errno));
287 // _exit(1);
288 //}
289
290 if (mount ("tmpfs", new_root, "tmpfs", 0, NULL) <= -1)
291 {
292 eio_unix_fork_error (errors, "pivot_root-tmpfs", strerror (errno));
293 _exit (1);
294 }
295 }
296 else
297 {
298 // From pivot_root example: we check that new_root is indeed a mountpoint
299 if (mount (new_root, new_root, NULL, MS_BIND, NULL) <= -1)
300 {
301 eio_unix_fork_error (errors, "pivot_root-new_root",
302 strerror (errno));
303 _exit (1);
304 }
305 }
306
307 // Make the place to pivot the old root too, under the new root
308 snprintf (old_root_path, sizeof (path), "%s/%s", new_root, put_old);
309
310 if (mkdir (old_root_path, 0777) == -1)
311 {
312 eio_unix_fork_error (errors, "pivot_root-mkdir-put_old",
313 strerror (errno));
314 _exit (1);
315 }
316
317 // Pivot the root
318 if (pivot_root (new_root, old_root_path))
319 {
320 eio_unix_fork_error (errors, "pivot_root", strerror (errno));
321 _exit (1);
322 }
323
324 // Add mounts
325 value current_mount = v_mounts;
326 int mount_result;
327 int mode;
328 while (current_mount != Val_emptylist)
329 {
330 // TODO: Mode for mounting
331 mode = Int_val (Field (Field (current_mount, 0), 2));
332
333 // A mount is a record {src; tgt; mode}, we first create the mount point
334 // directory target
335 if (mkdir (String_val (Field (Field (current_mount, 0), 1)), 0777) ==
336 -1)
337 {
338 eio_unix_fork_error (errors, "pivot_root-mkdir-mount",
339 strerror (errno));
340 _exit (1);
341 }
342
343 mount_result = mount (String_val (Field (Field (current_mount, 0), 0)),
344 String_val (Field (Field (current_mount, 0), 1)),
345 NULL, MS_REC | MS_BIND, NULL);
346
347 // Fail early if a mount fails...
348 if (mount_result < 0)
349 {
350 char error[PATH_MAX];
351 snprintf (error, sizeof (error), "mount failed: (%s->%s)",
352 String_val (Field (Field (current_mount, 0), 0)),
353 String_val (Field (Field (current_mount, 0), 1)));
354 eio_unix_fork_error (errors, error, strerror (errno));
355 _exit (1);
356 }
357
358 // After mounting for the first time, we can come back and add any
359 // extra modes that may have been specified, for example RDONLY.
360 if (mode != 0)
361 {
362 mount_result =
363 mount (String_val (Field (Field (current_mount, 0), 0)),
364 String_val (Field (Field (current_mount, 0), 1)), NULL,
365 MS_REMOUNT | MS_BIND | mode, NULL);
366
367 if (mount_result < 0)
368 {
369 eio_unix_fork_error (errors, "remount for mode",
370 strerror (errno));
371 _exit (1);
372 }
373 }
374
375 // Next mount in the list
376 current_mount = Field (current_mount, 1);
377 }
378
379
380 // Change to the 'new' root
381 if (chdir ("/") == -1)
382 {
383 eio_unix_fork_error (errors, "pivot_root-chdir", strerror (errno));
384 _exit (1);
385 }
386
387 // Unmount the old root and remove it
388 if (umount2 (put_old, MNT_DETACH) == -1)
389 {
390 eio_unix_fork_error (errors, put_old, strerror (errno));
391 _exit (1);
392 }
393
394 // Remove the old root
395 if (rmdir (put_old) == -1)
396 {
397 eio_unix_fork_error (errors, put_old, strerror (errno));
398 _exit (1);
399 }
400
401
402 // Apply any flags to the new root, e.g. RDONLY
403 if (Int_val (v_root_flags))
404 {
405 if (mount
406 ("/", "/", NULL, (MS_REMOUNT | MS_BIND | Int_val (v_root_flags)),
407 NULL) <= -1)
408 {
409 eio_unix_fork_error (errors, "pivot_root-rootflags",
410 strerror (errno));
411 _exit (1);
412 }
413 }
414}
415
416CAMLprim value
417void_fork_pivot_root (value v_unit)
418{
419 return Val_fork_fn (action_pivot_root);
420}