projects/guix·blob javascript-team · refs

899 lines · 30.2 KiB

1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2025 Caleb Ristvedt <[email protected]>
3
4 This file is part of GNU Guix.
5
6 GNU Guix is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Guix is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. */
18
19/* Process spawning and setup code. */
20
21#include <spawn.hh>
22#include <util.hh>
23#include <affinity.hh>
24#include <stddef.h>
25#include <unistd.h>
26#include <grp.h>
27#include <limits.h>
28#include <sys/wait.h>
29#include <fcntl.h>
30#include <cstring>
31#include <cstdlib>
32#include <cassert>
33#include <format>
34
35#if HAVE_SYS_MOUNT_H
36#include <sys/mount.h>
37#endif
38
39#if HAVE_SCHED_H
40#include <sched.h>
41#endif
42
43#if HAVE_STATVFS
44#include <sys/statvfs.h>
45#endif
46
47#if HAVE_SYS_SYSCALL_H
48#include <sys/syscall.h>
49#endif
50
51#if HAVE_SYS_PRCTL_H
52#include <sys/prctl.h>
53#endif
54
55#ifdef __linux__
56#include <sys/personality.h>
57#include <linux/seccomp.h>
58#include <linux/filter.h>
59#endif
60
61#if defined(SYS_pivot_root)
62#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root,put_old))
63#endif
64
65#if defined(CLONE_NEWNS)
66#define CLONE_ENABLED 1
67#else
68#define CLONE_ENABLED 0
69#endif
70
71#if CLONE_ENABLED
72#include <sys/ioctl.h>
73#include <net/if.h>
74#include <netinet/in.h>
75#endif
76
77namespace nix {
78
79
80void addPhaseAfter(Phases & phases, string afterLabel, string addLabel, Action addAction)
81{
82 for(auto i = phases.begin(); i != phases.end(); i++)
83 if((*i).label == afterLabel) {
84 i++; /* std::vector::insert inserts before, not after */
85 Phase p;
86 p.label = addLabel;
87 p.action = addAction;
88 phases.insert(i, p);
89 return;
90 }
91 throw Error(std::format("label `{}' not found in phases", afterLabel));
92}
93
94
95void addPhaseBefore(Phases & phases, string beforeLabel, string addLabel, Action addAction)
96{
97 for(auto i = phases.begin(); i != phases.end(); i++)
98 if((*i).label == beforeLabel) {
99 Phase p;
100 p.label = addLabel;
101 p.action = addAction;
102 phases.insert(i, p);
103 return;
104 }
105 throw Error(std::format("label `{}' not found in phases", beforeLabel));
106}
107
108
109void prependPhase(Phases & phases, string addLabel, Action addAction)
110{
111 Phase p;
112 p.label = addLabel;
113 p.action = addAction;
114 phases.insert(phases.begin(), p);
115}
116
117
118void appendPhase(Phases & phases, string addLabel, Action addAction)
119{
120 Phase p;
121 p.label = addLabel;
122 p.action = addAction;
123 phases.push_back(p);
124}
125
126
127void deletePhase(Phases & phases, string delLabel)
128{
129 for(auto i = phases.begin(); i != phases.end(); i++)
130 if((*i).label == delLabel) {
131 phases.erase(i);
132 return;
133 }
134 throw Error(std::format("label `{}' not found in phases", delLabel));
135}
136
137
138void replacePhase(Phases & phases, string replaceLabel, Action newAction)
139{
140 for(auto i = phases.begin(); i != phases.end(); i++)
141 if((*i).label == replaceLabel) {
142 (*i).action = newAction;
143 return;
144 }
145 throw Error(std::format("label `{}' not found in phases", replaceLabel));
146}
147
148
149/* A curated selection of predefined actions */
150
151static void reset_writeToStderrAction(SpawnContext & ctx)
152{
153 _writeToStderr = 0;
154}
155
156
157static void restoreAffinityAction(SpawnContext & ctx)
158{
159 restoreAffinity();
160}
161
162
163static void setsidAction(SpawnContext & ctx)
164{
165 /* Puts the current process in a separate session, which implies a
166 separate process group, so it doesn't receive group-directed signals
167 sent at the parent. The new session initially has no controlling
168 terminal, so it also doesn't receive terminal signals and can't open
169 /dev/tty. */
170 if(ctx.setsid && setsid() == (pid_t)-1)
171 throw SysError("creating a new session");
172}
173
174
175static void earlyIOSetupAction(SpawnContext & ctx)
176{
177 for(auto i = ctx.earlyCloseFDs.begin(); i != ctx.earlyCloseFDs.end(); i++)
178 if(close(*i) == -1)
179 throw SysError("closing fd");
180
181 if(ctx.logFD != -1) {
182 if(dup2(ctx.logFD, STDOUT_FILENO) == -1)
183 throw SysError("cannot dup2 log fd into stdout fd");
184 if(dup2(ctx.logFD, STDERR_FILENO) == -1)
185 throw SysError("cannot dup2 log fd into stderr fd");
186 }
187
188 if(ctx.setStdin) {
189 if(ctx.stdinFD != -1) {
190 if(dup2(ctx.stdinFD, STDIN_FILENO) == -1)
191 throw SysError("cannot dup2 fd into stdin fd");
192 }
193 else {
194 /* Doesn't make sense for it to be writable, but compatibility... */
195 AutoCloseFD fd = open(ctx.stdinFile.c_str(), O_RDWR);
196 if(fd == -1)
197 throw SysError(std::format("cannot open `{}'", ctx.stdinFile));
198 if(dup2(fd, STDIN_FILENO) == -1)
199 throw SysError("cannot dup2 fd into stdin fd");
200 }
201 }
202}
203
204
205static void dropAmbientCapabilitiesAction(SpawnContext & ctx)
206{
207 /* Drop ambient capabilities such as CAP_CHOWN that might have been granted
208 when starting guix-daemon. */
209 if(ctx.dropAmbientCapabilities)
210#if HAVE_SYS_PRCTL_H
211 prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
212#else
213 throw Error("dropping ambient capabilities is not supported on this system");
214#endif
215}
216
217
218static void chrootAction(SpawnContext & ctx)
219{
220 if(ctx.doChroot)
221#if HAVE_CHROOT
222 if(chroot(ctx.chrootRootDir.c_str()) == -1)
223 throw SysError(std::format("cannot change root directory to '{}'", ctx.chrootRootDir));
224#else
225 throw Error("chroot is not supported on this system");
226#endif
227}
228
229
230static void chdirAction(SpawnContext & ctx)
231{
232 if(ctx.setcwd)
233 if(chdir(ctx.cwd.c_str()) == -1)
234 throw SysError(std::format("changing into `{}'", ctx.cwd));
235}
236
237
238static void closeMostFDsAction(SpawnContext & ctx)
239{
240 if(ctx.closeMostFDs) closeMostFDs(ctx.preserveFDs);
241 for(auto i = ctx.preserveFDs.begin(); i != ctx.preserveFDs.end(); i++)
242 keepOnExec(*i);
243}
244
245
246static void setPersonalityAction(SpawnContext & ctx)
247{
248 if(ctx.setPersona)
249#ifdef __linux__
250 if(personality(ctx.persona) == -1)
251 throw SysError("cannot set personality");
252#else
253 throw Error("setting the personality is not supported on this system");
254#endif
255}
256
257
258static void oomSacrificeAction(SpawnContext & ctx)
259{
260#ifdef __linux__
261 if(ctx.oomSacrifice)
262 /* Ask the kernel to eagerly kill us & our children if it runs out of
263 memory, regardless of blame, to preserve ‘real’ user data &
264 state. */
265 try {
266 writeFile("/proc/self/oom_score_adj", "1000"); // 100%
267 } catch(...) { ignoreException(); }
268#endif
269}
270
271
272static void setIDsAction(SpawnContext & ctx)
273{
274 if(ctx.setSupplementaryGroups)
275 if(setgroups(ctx.supplementaryGroups.size(),
276 ctx.supplementaryGroups.data()) == -1)
277 throw SysError("cannot set supplementary groups");
278
279 if(ctx.setgid)
280 if(setgid(ctx.group) == -1 ||
281 getgid() != ctx.group ||
282 getegid() != ctx.group)
283 throw SysError("setgid failed");
284
285 if(ctx.setuid)
286 if(setuid(ctx.user) == -1 ||
287 getuid() != ctx.user ||
288 geteuid() != ctx.user)
289 throw SysError("setuid failed");
290}
291
292static void setNoNewPrivsAction(SpawnContext & ctx)
293{
294 if(ctx.setNoNewPrivs)
295#if __linux__ && defined(PR_SET_NO_NEW_PRIVS)
296 if(prctl(PR_SET_NO_NEW_PRIVS, 0, 0, 0, 0) == -1)
297 throw SysError("setting PR_SET_NO_NEW_PRIVS");
298#else
299 throw Error("setting PR_SET_NO_NEW_PRIVS not supported on this system");
300#endif
301}
302
303static void addSeccompFilterAction(SpawnContext & ctx)
304{
305 if(ctx.addSeccompFilter) {
306#if __linux__ && defined(PR_SET_SECCOMP) && defined(SECCOMP_MODE_FILTER)
307 /* We use no extra functionality from the seccomp system call, so
308 * just use prctl. */
309 if(ctx.seccompFilter.size() > USHRT_MAX)
310 throw Error("seccomp filter too large");
311 struct sock_fprog prog;
312 prog.len = (unsigned short) ctx.seccompFilter.size();
313 prog.filter = ctx.seccompFilter.data();
314 if(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1)
315 throw SysError("installing seccomp filter");
316#else
317 throw Error("setting seccomp filter not supported on this system");
318#endif
319 }
320}
321
322
323static void restoreSIGPIPEAction(SpawnContext & ctx)
324{
325 /* Restore default handling of SIGPIPE, otherwise some programs will
326 randomly say "Broken pipe". */
327 struct sigaction act, oact;
328 act.sa_handler = SIG_DFL;
329 act.sa_flags = 0;
330 sigemptyset(&act.sa_mask);
331 if (sigaction(SIGPIPE, &act, &oact)) throw SysError("resetting SIGPIPE");
332}
333
334
335static void setupSuccessAction(SpawnContext & ctx)
336{
337 if(ctx.signalSetupSuccess)
338 writeFull(STDERR_FILENO, "\n");
339}
340
341
342void execAction(SpawnContext & ctx) // kept public for use in 'build.cc'
343{
344 Strings envStrs;
345 std::vector<char *> envPtrs;
346 char **env;
347 if(ctx.inheritEnv) {
348 for(auto i = ctx.env.begin(); i != ctx.env.end(); i++)
349 if(setenv(i->first.c_str(), i->second.c_str(), 1) == -1)
350 throw SysError("setenv");
351 env = environ;
352 } else {
353 for(auto i = ctx.env.begin(); i != ctx.env.end(); i++)
354 envStrs.push_back(i->first + "=" + i->second);
355 /* Need to keep the envPtrs vector alive as long as its .data()! */
356 envPtrs = stringsToCharPtrs(envStrs);
357 env = envPtrs.data();
358 }
359 if(execvpe(ctx.program.c_str(), stringsToCharPtrs(ctx.args).data(), env) == -1)
360 throw SysError(std::format("executing `{}'", ctx.program));
361}
362
363
364Phases getBasicSpawnPhases()
365{
366 return { { "reset_writeToStderr", reset_writeToStderrAction },
367 { "restoreAffinity", restoreAffinityAction },
368 { "setsid", setsidAction },
369 { "earlyIOSetup", earlyIOSetupAction },
370 { "dropAmbientCapabilities", dropAmbientCapabilitiesAction },
371 { "chroot", chrootAction },
372 { "chdir", chdirAction },
373 { "closeMostFDs", closeMostFDsAction },
374 { "setPersonality", setPersonalityAction },
375 { "oomSacrifice", oomSacrificeAction },
376 { "setIDs", setIDsAction },
377 { "setNoNewPrivs", setNoNewPrivsAction },
378 { "addSeccompFilter", addSeccompFilterAction },
379 { "restoreSIGPIPE", restoreSIGPIPEAction },
380 { "setupSuccess", setupSuccessAction },
381 { "exec", execAction } };
382}
383
384
385static void usernsInitSyncAction(SpawnContext & sctx)
386{
387#if CLONE_ENABLED
388 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
389 if((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
390 /* Close the earlyCloseFDs before we try reading anything */
391 for(auto i = ctx.earlyCloseFDs.begin(); i != ctx.earlyCloseFDs.end(); i++)
392 if(close(*i) == -1)
393 throw SysError("closing fd");
394 /* Don't try closing them again later */
395 ctx.earlyCloseFDs.clear();
396 /* Wait for the parent process to initialize the UID/GID mapping of
397 our user namespace. */
398 waitForMessage(ctx.setupFD, "go\n");
399 }
400#endif
401}
402
403
404static void usernsSetIDsAction(SpawnContext & sctx)
405{
406#if CLONE_ENABLED
407 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
408 if((ctx.cloneFlags & CLONE_NEWUSER) != 0) {
409 /* Note: 'man capabilities' says that a transition from zero to
410 nonzero uids causes capabilities to be lost, but doesn't say what
411 happens when a transition from an unmapped (possibly zero) uid to a
412 nonzero uid happens. */
413 if(ctx.usernsSetuid)
414 /* Since we presumably have CAP_SETUID, this sets the real,
415 effective, saved, and filesystem uids */
416 if(setuid(ctx.usernsUser) != 0)
417 throw SysError("setuid");
418 if(ctx.usernsSetgid)
419 /* Ditto but with gids */
420 if(setgid(ctx.usernsGroup) != 0)
421 throw SysError("setgid");
422 }
423#endif
424}
425
426
427static void initLoopbackAction(SpawnContext & sctx)
428{
429#if CLONE_ENABLED
430 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
431 if(((ctx.cloneFlags & CLONE_NEWNET) != 0) && ctx.initLoopback) {
432 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
433 if (fd == -1) throw SysError("cannot open IP socket");
434
435 struct ifreq ifr;
436 strcpy(ifr.ifr_name, "lo");
437 ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
438 if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1)
439 throw SysError("cannot set loopback interface flags");
440
441 fd.close();
442 }
443#endif
444}
445
446
447static void setHostAndDomainAction(SpawnContext & sctx)
448{
449#if CLONE_ENABLED
450 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
451 if((ctx.cloneFlags & CLONE_NEWUTS) != 0) {
452 if (sethostname(ctx.hostname.c_str(),
453 strlen(ctx.hostname.c_str())) == -1)
454 throw SysError("cannot set host name");
455 if (setdomainname(ctx.domainname.c_str(),
456 strlen(ctx.domainname.c_str())) == -1)
457 throw SysError("cannot set domain name");
458 }
459#endif
460}
461
462
463static void makeFilesystemsPrivateAction(SpawnContext & sctx)
464{
465#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_REC) && defined(MS_PRIVATE)
466 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
467 if((ctx.cloneFlags & CLONE_NEWNS) != 0) {
468 if(mount(0, "/", 0, MS_REC|MS_PRIVATE, 0) == -1)
469 throw SysError("unable to make `/' private mount");
470 }
471#endif
472}
473
474
475static void makeChrootSeparateFilesystemAction(SpawnContext & sctx)
476{
477#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_BIND)
478 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
479 if(((ctx.cloneFlags & CLONE_NEWNS) != 0) && ctx.doChroot) {
480 /* Bind-mount chroot directory to itself, to treat it as a different
481 filesystem from /, as needed for pivot_root. Alternatively, mount
482 a tmpfs on it. */
483 if(ctx.mountTmpfsOnChroot) {
484 if(mount("none", ctx.chrootRootDir.c_str(), "tmpfs", 0, 0) == -1)
485 throw SysError(std::format("unable to mount tmpfs on `{}'", ctx.chrootRootDir));
486 }
487 else {
488 if(mount(ctx.chrootRootDir.c_str(), ctx.chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
489 throw SysError(std::format("unable to bind mount `{}'", ctx.chrootRootDir));
490 }
491 }
492#endif
493}
494
495
496static int statfsToMountFlags(int f_flags)
497{
498#if HAVE_SYS_MOUNT_H && HAVE_STATVFS
499 int ret = 0;
500#if defined(ST_RDONLY) && defined(MS_RDONLY)
501 if((f_flags & ST_RDONLY) != 0) ret |= MS_RDONLY;
502#endif
503#if defined(ST_NOSUID) && defined(MS_NOSUID)
504 if((f_flags & ST_NOSUID) != 0) ret |= MS_NOSUID;
505#endif
506#if defined(ST_NODEV) && defined(MS_NODEV)
507 if((f_flags & ST_NODEV) != 0) ret |= MS_NODEV;
508#endif
509#if defined(ST_NOEXEC) && defined(MS_NOEXEC)
510 if((f_flags & ST_NOEXEC) != 0) ret |= MS_NOEXEC;
511#endif
512#if defined(ST_NOATIME) && defined(MS_NOATIME)
513 if((f_flags & ST_NOATIME) != 0) ret |= MS_NOATIME;
514#endif
515#if defined(ST_NODIRATIME) && defined(MS_NODIRATIME)
516 if((f_flags & ST_NODIRATIME) != 0) ret |= MS_NODIRATIME;
517#endif
518#if defined(ST_RELATIME) && defined(MS_RELATIME)
519 if((f_flags & ST_RELATIME) != 0) ret |= MS_RELATIME;
520#endif
521 return ret;
522#else
523 throw Error("statfsToMountFlags not supported on this platform");
524#endif
525}
526
527
528static void bindMount(Path source, Path target, bool readOnly)
529{
530#if HAVE_SYS_MOUNT_H && defined(MS_BIND)
531 struct stat st;
532 if (lstat(source.c_str(), &st) == -1)
533 throw SysError(std::format("getting attributes of path `{}'", source));
534
535 if(S_ISDIR(st.st_mode))
536 createDirs(target);
537 else if(S_ISLNK(st.st_mode)) {
538 /* bind-mounts follow symlinks, thus representing their target and not
539 the symlink itself. Create a copy of the symlink instead.*/
540 createDirs(dirOf(target));
541 createSymlink(readLink(source), target);
542 return;
543 }
544 else {
545 struct stat st2;
546 createDirs(dirOf(target));
547 /* Alternate between trying to create placeholder file at target and
548 * checking for its existence and type */
549 while(true){
550 if(lstat(target.c_str(), &st2) != -1) {
551 if(!S_ISREG(st2.st_mode))
552 throw Error(std::format("mount target `{}' exists but is not a regular file", target));
553 break;
554 }
555 if(errno != ENOENT) {
556 throw SysError(std::format("stat'ing path `{}'", target));
557 }
558 AutoCloseFD fd = open(target.c_str(),
559 O_WRONLY | O_NOFOLLOW | O_CREAT | O_EXCL,
560 0600);
561 if(fd != -1) {
562 fd.close(); /* Now exists and is a fresh regular file */
563 break;
564 }
565 /* Note: because of O_CREAT | O_EXCL, EACCES can only mean a
566 * permission issue with the parent directory */
567 if(errno != EEXIST)
568 throw SysError(std::format("Creating placeholder regular file target mount `{}'", target));
569 }
570 }
571
572 /* This may fail with EINVAL unless we specify MS_REC, specifically if we
573 are in an unprivileged mount namespace and not specifying MS_REC would
574 reveal subtrees that had been covered up. */
575 if (mount(source.c_str(), target.c_str(), 0, MS_BIND|MS_REC, 0) == -1)
576 throw SysError(std::format("bind mount from `{}' to `{}' failed", source, target));
577 if(readOnly) {
578#if defined(MS_REMOUNT) && defined(MS_RDONLY)
579 /* Extra flags passed with MS_BIND are ignored, hence the extra
580 MS_REMOUNT. */
581 unsigned long mount_flags = MS_BIND | MS_REMOUNT | MS_RDONLY;
582 /* MS_BIND | MS_REMOUNT sets all mountpoint flags, so we may get EPERM
583 unless we preserve the other flags (for example because it would
584 result in trying to clear the nosuid flag). */
585#if HAVE_STATVFS
586 struct statvfs stvfs;
587 if(statvfs(target.c_str(), &stvfs) == -1)
588 throw SysError(std::format("statvfs of `{}'", target));
589 mount_flags |= statfsToMountFlags(stvfs.f_flag);
590#endif
591
592 if (mount(source.c_str(), target.c_str(), 0, mount_flags, 0) == -1)
593 throw SysError(std::format("read-only remount of `{}' failed", target));
594#else
595 throw Error("remounting read-only is not supported on this platform");
596#endif
597 }
598#endif
599}
600
601
602static void mountIntoChroot(std::map<Path, Path> filesInChroot,
603 set<Path> readOnlyFiles,
604 Path chrootRootDir)
605{
606#if HAVE_SYS_MOUNT_H && defined(MS_BIND)
607 for(auto i = filesInChroot.begin(); i != filesInChroot.end(); i++) {
608 Path source = i->second;
609 Path target = chrootRootDir + i->first;
610 bool readOnly = readOnlyFiles.find(i->first) != readOnlyFiles.end();
611 bindMount(source, target, readOnly);
612 }
613#else
614 throw Error("bind mounting not supported on this platform");
615#endif
616}
617
618
619static void mountIntoChrootAction(SpawnContext & sctx)
620{
621#if CLONE_ENABLED && HAVE_SYS_MOUNT_H && defined(MS_BIND)
622 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
623 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.doChroot) {
624 mountIntoChroot(ctx.filesInChroot, ctx.readOnlyFilesInChroot, ctx.chrootRootDir);
625 }
626#endif
627}
628
629
630static void mountProcAction(SpawnContext & sctx)
631{
632#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
633 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
634 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.mountProc) {
635 Path target = (ctx.doChroot ? ctx.chrootRootDir : "") + "/proc";
636 createDirs(target);
637 if(mount("none", target.c_str(), "proc", 0, 0) == -1)
638 throw SysError(std::format("mounting `{}'", target));
639 }
640#endif
641}
642
643
644static void mountDevshmAction(SpawnContext & sctx)
645{
646#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
647 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
648 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.mountDevshm) {
649 Path target = (ctx.doChroot ? ctx.chrootRootDir : "") + "/dev/shm";
650 createDirs(target);
651 if(mount("none", target.c_str(), "tmpfs", 0, 0) == -1)
652 throw SysError(std::format("mounting `{}'", target));
653 }
654#endif
655}
656
657
658static void mountDevptsAction(SpawnContext & sctx)
659{
660#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
661 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
662 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.maybeMountDevpts) {
663 Path chroot = (ctx.doChroot ? ctx.chrootRootDir : "");
664 Path target = chroot + "/dev/pts";
665 if(pathExists(chroot + "/dev/ptmx")) return;
666 createDirs(target);
667 if(mount("none", target.c_str(), "devpts", 0, "newinstance,mode=0620") == -1)
668 throw SysError(std::format("mounting `{}'", target));
669 createSymlink("/dev/pts/ptmx", chroot + "/dev/ptmx");
670 /* Make sure /dev/pts/ptmx is world-writable. With some Linux
671 versions, it is created with permissions 0. */
672 Path targetPtmx = chroot + "/dev/pts/ptmx";
673 if (chmod(targetPtmx.c_str(), 0666) == -1)
674 throw SysError(std::format("setting permissions on `{}'", targetPtmx));
675 }
676#endif
677}
678
679
680static void pivotRootAction(SpawnContext & sctx)
681{
682#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
683 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
684 if((ctx.cloneFlags & CLONE_NEWNS) != 0 && ctx.doChroot) {
685 if (chdir(ctx.chrootRootDir.c_str()) == -1)
686 throw SysError(std::format("cannot change directory to '{}'", ctx.chrootRootDir));
687
688 if (mkdir("real-root", 0) == -1)
689 throw SysError("cannot create real-root directory");
690
691 if (pivot_root(".", "real-root") == -1)
692 throw SysError(std::format("cannot pivot old root directory onto '{}'", (ctx.chrootRootDir + "/real-root")));
693
694 if (chroot(".") == -1)
695 throw SysError(std::format("cannot change root directory to '{}'", ctx.chrootRootDir));
696
697 if (umount2("real-root", MNT_DETACH) == -1)
698 throw SysError("cannot unmount real root filesystem");
699
700 if (rmdir("real-root") == -1)
701 throw SysError("cannot remove real-root directory");
702 }
703#endif
704}
705
706
707static string idMapToIdentityMap(const string & map)
708{
709 std::vector<string> mapLines =
710 tokenizeString<std::vector<string> >(map, "\n");
711 string out;
712
713 for(auto & i : mapLines) {
714 std::vector<string> elements =
715 tokenizeString<std::vector<string> >(i, " ");
716 out.append(elements.at(0) + " " + elements.at(0) + " " + elements.at(2) + "\n");
717 }
718 return out;
719}
720
721
722/* Initializing a user namespace with more than one id mapped requires
723 * capabilities in the *parent* user namespace, which may not even have any
724 * processes in it after unshare is called. So fork a child and have it do
725 * the initialization. */
726void unshareAndInitUserns(int flags, const string & uidMap,
727 const string & gidMap, bool allowSetgroups)
728{
729#if CLONE_ENABLED
730 pid_t pid_ = getpid();
731 string pid = std::to_string(pid_);
732 Pipe toChild;
733 Pipe fromChild;
734 toChild.create();
735 fromChild.create();
736 pid_t child = fork();
737 if(child == -1)
738 throw SysError("creating child process");
739 if(child == 0) {
740 try {
741 toChild.writeSide.close();
742 fromChild.readSide.close();
743 waitForMessage(toChild.readSide, "ready\n");
744 writeFile("/proc/" + pid + "/uid_map", uidMap);
745 writeFile("/proc/" + pid + "/setgroups",
746 allowSetgroups ? "allow" : "deny");
747 writeFile("/proc/" + pid + "/gid_map", gidMap);
748 writeFull(fromChild.writeSide, (unsigned char*)"go\n", 3);
749 } catch(...) {
750 /* Don't unwind the stack in case of exception, halt
751 * immediately. */
752 _exit(1);
753 }
754 _exit(EXIT_SUCCESS);
755 } else {
756 toChild.readSide.close();
757 fromChild.writeSide.close();
758 if(unshare(flags) == -1)
759 throw SysError("unshare");
760 writeFull(toChild.writeSide, (unsigned char*)"ready\n", 6);
761 waitForMessage(fromChild.readSide, "go\n");
762 int status;
763 while(waitpid(child, &status, 0) == -1) {
764 if(errno != EINTR)
765 throw SysError("reaping userns init process");
766 }
767 if(!(WIFEXITED(status) != 0 && WEXITSTATUS(status) == EXIT_SUCCESS))
768 throw Error(std::format("userns init child exited with status {}", WEXITSTATUS(status)));
769 }
770#endif
771}
772
773
774static void lockMountsAction(SpawnContext & sctx)
775{
776#if CLONE_ENABLED && HAVE_SYS_MOUNT_H
777 CloneSpawnContext & ctx = (CloneSpawnContext &) sctx;
778 if(ctx.lockMounts) {
779 string uidMap;
780 string gidMap;
781 if(ctx.lockMountsMapAll) {
782 string oldUidMap = readFile("/proc/self/uid_map", true);
783 string oldGidMap = readFile("/proc/self/gid_map", true);
784 uidMap = idMapToIdentityMap(oldUidMap);
785 gidMap = idMapToIdentityMap(oldGidMap);
786 } else {
787 string uid = std::to_string(getuid());
788 string gid = std::to_string(getgid());
789 uidMap = uid + " " + uid + " 1";
790 gidMap = gid + " " + gid + " 1";
791 }
792 unshareAndInitUserns(CLONE_NEWNS | CLONE_NEWUSER,
793 uidMap, gidMap, ctx.lockMountsAllowSetgroups);
794 /* Check that mounts inherited in our new mount namespace are "locked"
795 together and cannot be separated from within our mount namespace.
796 Since umount(2) is documented to fail with EINVAL when attempting
797 to unmount one of the mounts that are locked together, check that
798 this is what we get. */
799 int ret = umount("/proc");
800 assert(ret == -1 && errno == EINVAL);
801 }
802#endif
803}
804
805
806Phases getCloneSpawnPhases()
807{
808#if CLONE_ENABLED
809 return { { "reset_writeToStderr", reset_writeToStderrAction },
810 { "usernsInitSync", usernsInitSyncAction },
811 { "usernsSetIDs", usernsSetIDsAction },
812 { "restoreAffinity", restoreAffinityAction },
813 { "setsid", setsidAction },
814 { "earlyIOSetup", earlyIOSetupAction },
815 { "dropAmbientCapabilities", dropAmbientCapabilitiesAction },
816 { "initLoopback", initLoopbackAction },
817 { "setHostAndDomain", setHostAndDomainAction },
818 { "makeFilesystemsPrivate", makeFilesystemsPrivateAction },
819 { "makeChrootSeparateFilesystem", makeChrootSeparateFilesystemAction },
820 { "mountIntoChroot", mountIntoChrootAction },
821 { "mountProc", mountProcAction },
822 { "mountDevshm", mountDevshmAction },
823 { "mountDevpts", mountDevptsAction },
824 { "chroot", pivotRootAction },
825 { "chdir", chdirAction },
826 { "closeMostFDs", closeMostFDsAction },
827 { "setPersonality", setPersonalityAction },
828 { "oomSacrifice", oomSacrificeAction },
829 /* Being put in a user namespace with only the current ids mapped
830 would tend to prevent switching to other ones, but if this
831 comes after setIDs then the per-process "dumpable" flag may be
832 reset, which will cause /proc/self to become root-owned,
833 making /proc/self/uid_map inaccessible. If you need
834 lockMounts to preserve the id mappings, and you have the
835 necessary capabilities in the parent user namespace, set
836 CloneSpawnContext.lockMountsMapAll = true. */
837 { "lockMounts", lockMountsAction },
838 { "setIDs", setIDsAction },
839 { "setNoNewPrivs", setNoNewPrivsAction },
840 { "addSeccompFilter", addSeccompFilterAction },
841 { "restoreSIGPIPE", restoreSIGPIPEAction },
842 { "setupSuccess", setupSuccessAction },
843 { "exec", execAction }};
844#else
845 throw Error("clone not supported on this platform");
846#endif
847}
848
849
850void runChildSetup(SpawnContext & ctx)
851{
852 ctx.currentPhase = 0;
853 try {
854 /* Should not return regularly from this */
855 while(true) {
856 ctx.phases.at(ctx.currentPhase).action(ctx);
857 ctx.currentPhase++;
858 }
859 } catch (std::exception & e) {
860 try {
861 writeFull(STDERR_FILENO,
862 "while setting up the child process: " +
863 (ctx.currentPhase < (ssize_t)ctx.phases.size() ?
864 "in phase " + ctx.phases[ctx.currentPhase].label + ": " : "") +
865 string(e.what()) + "\n");
866 } catch (std::exception & e2) {
867 _exit(1);
868 }
869 _exit(1);
870 }
871 abort(); /* Should never be reached */
872}
873
874
875static int runChildSetupEntry(void *data)
876{
877 runChildSetup(* (SpawnContext *)data);
878 return 1;
879}
880
881
882int cloneChild(CloneSpawnContext & ctx)
883{
884#if CLONE_ENABLED
885 char stack[32 * 1024];
886 /* Ensure proper alignment on the stack. On aarch64, it has to be 16
887 bytes. */
888 char *alignedStack = (char *)(((uintptr_t)stack + sizeof(stack) - 8) & ~(uintptr_t)0xf);
889 int ret = clone(runChildSetupEntry, alignedStack, ctx.cloneFlags, (void *) &ctx);
890 if(ret == -1)
891 throw SysError("clone");
892 return ret;
893#else
894 throw Error("clone not supported on this platform");
895#endif
896}
897
898
899}