/ src / darlingserver.cpp
darlingserver.cpp
  1  /**
  2   * This file is part of Darling.
  3   *
  4   * Copyright (C) 2021 Darling developers
  5   *
  6   * Darling is free software: you can redistribute it and/or modify
  7   * it under the terms of the GNU General Public License as published by
  8   * the Free Software Foundation, either version 3 of the License, or
  9   * (at your option) any later version.
 10   *
 11   * Darling is distributed in the hope that it will be useful,
 12   * but 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 Darling.  If not, see <http://www.gnu.org/licenses/>.
 18   */
 19  
 20  #define _GNU_SOURCE 1
 21  #include <stdio.h>
 22  #include <unistd.h>
 23  #include <sched.h>
 24  #include <stdlib.h>
 25  #include <sys/mount.h>
 26  #include <errno.h>
 27  #include <string.h>
 28  #include <stdbool.h>
 29  #include <sys/prctl.h>
 30  #include <dirent.h>
 31  #include <sys/stat.h>
 32  #include <pwd.h>
 33  #include <sys/mman.h>
 34  #include <fcntl.h>
 35  #include <sys/resource.h>
 36  #include <iostream>
 37  #include <linux/sched.h>
 38  #include <sys/syscall.h>
 39  #include <sys/signal.h>
 40  #include <filesystem>
 41  
 42  #include <darling-config.h>
 43  
 44  #include <darlingserver/server.hpp>
 45  #include <darlingserver/config.hpp>
 46  
 47  #ifndef DARLINGSERVER_INIT_PROCESS
 48  	#define DARLINGSERVER_INIT_PROCESS "/sbin/launchd"
 49  #endif
 50  
 51  #ifndef DARLINGSERVER_XDG_USER_DIR_CMD
 52  	#define DARLINGSERVER_XDG_USER_DIR_CMD "xdg-user-dir"
 53  #endif
 54  
 55  #if DSERVER_ASAN
 56  	#include <sanitizer/lsan_interface.h>
 57  #endif
 58  
 59  // TODO: most of the code here was ported over from startup/darling.c; we should C++-ify it.
 60  
 61  void fixPermissionsRecursive(const char* path, uid_t originalUID, gid_t originalGID)
 62  {
 63  	DIR* dir;
 64  	struct dirent* ent;
 65  
 66  	if (chown(path, originalUID, originalGID) == -1)
 67  		fprintf(stderr, "Cannot chown %s: %s\n", path, strerror(errno));
 68  
 69  	dir = opendir(path);
 70  	if (!dir)
 71  		return;
 72  
 73  	while ((ent = readdir(dir)) != NULL)
 74  	{
 75  		if (ent->d_type == DT_DIR)
 76  		{
 77  			char* subdir;
 78  
 79  			if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
 80  				continue;
 81  
 82  			subdir = (char*) malloc(strlen(path) + 2 + strlen(ent->d_name));
 83  			sprintf(subdir, "%s/%s", path, ent->d_name);
 84  
 85  			fixPermissionsRecursive(subdir, originalUID, originalGID);
 86  
 87  			free(subdir);
 88  		}
 89  	}
 90  
 91  	closedir(dir);
 92  }
 93  
 94  const char* xdgDirectory(const char* name)
 95  {
 96  	static char dir[4096];
 97  	char* cmd = (char*) malloc(sizeof(DARLINGSERVER_XDG_USER_DIR_CMD) + 1 + strlen(name));
 98  
 99  	sprintf(cmd, DARLINGSERVER_XDG_USER_DIR_CMD " %s", name);
100  
101  	FILE* proc = popen(cmd, "r");
102  
103  	free(cmd);
104  
105  	if (!proc)
106  		return NULL;
107  
108  	fgets(dir, sizeof(dir)-1, proc);
109  
110  	pclose(proc);
111  
112  	size_t len = strlen(dir);
113  	if (len <= 1)
114  		return NULL;
115  
116  	if (dir[len-1] == '\n')
117  		dir[len-1] = '\0';
118  	return dir;
119  }
120  
121  void setupUserHome(const char* prefix, uid_t originalUID)
122  {
123  	char buf[4096], buf2[4096];
124  
125  	snprintf(buf, sizeof(buf), "%s/Users", prefix);
126  
127  	// Remove the old /Users symlink that may exist
128  	unlink(buf);
129  
130  	// mkdir /Users
131  	mkdir(buf, 0777);
132  
133  	// mkdir /Users/Shared
134  	strcat(buf, "/Shared");
135  	mkdir(buf, 0777);
136  
137  	const char* home = getenv("HOME");
138  
139  	const char* login = NULL;
140  	struct passwd* pw = getpwuid(originalUID);
141  
142  	if (pw != NULL)
143  		login = pw->pw_name;
144  
145  	if (!login)
146  		login = getlogin();
147  
148  	if (!login)
149  	{
150  		fprintf(stderr, "Cannot determine your user name\n");
151  		exit(1);
152  	}
153  	if (!home)
154  	{
155  		fprintf(stderr, "Cannot determine your home directory\n");
156  		exit(1);
157  	}
158  
159  	snprintf(buf, sizeof(buf), "%s/Users/%s", prefix, login);
160  
161  	// mkdir /Users/$LOGIN
162  	mkdir(buf, 0755);
163  
164  	snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", home);
165  
166  	strcat(buf, "/LinuxHome");
167  	unlink(buf);
168  
169  	// symlink /Users/$LOGIN/LinuxHome -> $HOME
170  	symlink(buf2, buf);
171  
172  	static const char* xdgmap[][2] = {
173  		{ "DESKTOP", "Desktop" },
174  		{ "DOWNLOAD", "Downloads" },
175  		{ "PUBLICSHARE", "Public" },
176  		{ "DOCUMENTS", "Documents" },
177  		{ "MUSIC", "Music" },
178  		{ "PICTURES", "Pictures" },
179  		{ "VIDEOS", "Movies" },
180  	};
181  
182  	for (int i = 0; i < sizeof(xdgmap) / sizeof(xdgmap[0]); i++)
183  	{
184  		const char* dir = xdgDirectory(xdgmap[i][0]);
185  		if (!dir)
186  			continue;
187  
188  		snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", dir);
189  		snprintf(buf, sizeof(buf), "%s/Users/%s/%s", prefix, login, xdgmap[i][1]);
190  
191  		unlink(buf);
192  		symlink(buf2, buf);
193  	}
194  }
195  
196  void setupCoredumpPattern(void)
197  {
198  	FILE* f = fopen("/proc/sys/kernel/core_pattern", "w");
199  	if (f != NULL)
200  	{
201  		// This is how macOS saves core dumps
202  		fputs("/cores/core.%p\n", f);
203  		fclose(f);
204  	}
205  }
206  
207  static void wipeDir(const char* dirpath)
208  {
209  	char path[4096];
210  	struct dirent* ent;
211  	DIR* dir = opendir(dirpath);
212  
213  	if (!dir)
214  		return;
215  
216  	while ((ent = readdir(dir)) != NULL)
217  	{
218  		if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
219  			continue;
220  
221  		snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name);
222  
223  		if (ent->d_type == DT_DIR)
224  		{
225  			wipeDir(path);
226  			rmdir(path);
227  		}
228  		else
229  			unlink(path);
230  	}
231  
232  	closedir(dir);
233  }
234  
235  void darlingPreInit(const char* prefix)
236  {
237  	// TODO: Run /usr/libexec/makewhatis
238  	const char* dirs[] = {
239  		"/var/tmp",
240  		"/var/run"
241  	};
242  
243  	char fullpath[4096];
244  	strcpy(fullpath, prefix);
245  	const size_t prefixLen = strlen(fullpath);
246  
247  	for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++)
248  	{
249  		fullpath[prefixLen] = 0;
250  		strcat(fullpath, dirs[i]);
251  		wipeDir(fullpath);
252  	}
253  }
254  
255  void spawnLaunchd(const char* prefix)
256  {
257  	puts("Bootstrapping the container with launchd...");
258  
259  	// putenv("KQUEUE_DEBUG=1");
260  
261  	auto tmp = (std::string(prefix) + "/.darlingserver.sock");
262  
263  	const char* initPath = getenv("DSERVER_INIT");
264  
265  	if (!initPath) {
266  		initPath = DARLINGSERVER_INIT_PROCESS;
267  	}
268  
269  	setenv("DYLD_ROOT_PATH", LIBEXEC_PATH, 1);
270  	setenv("__mldr_sockpath", tmp.c_str(), 1);
271  	execl(DarlingServer::Config::defaultMldrPath.data(), "mldr!" LIBEXEC_PATH "/usr/libexec/darling/vchroot", "vchroot", prefix, initPath, NULL);
272  
273  	fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno));
274  	abort();
275  }
276  
277  static bool testEnvVar(const char* var_name) {
278  	const char* var = getenv(var_name);
279  
280  	if (!var) {
281  		return false;
282  	}
283  
284  	auto len = strlen(var);
285  
286  	if (len > 0 && (var[0] == '1' || var[0] == 't' || var[0] == 'T')) {
287  		return true;
288  	}
289  
290  	return false;
291  };
292  
293  static bool isOnWsl1() {
294  	static bool initialized = false;
295  	static bool result = false;
296  	if (!initialized) {
297  		initialized = true;
298  		// All WSL systems have WSLENV set by default, while WSL_INTEROP is WSL2-specific.
299  		result = getenv("WSLENV") && !getenv("WSL_INTEROP");
300  	}
301  
302  	return result;
303  }
304  
305  static bool shouldUseOverlayFs() {
306  	bool shouldUse = true;
307  	bool explicitlySet = false;
308  
309  	if (testEnvVar("DARLING_NOOVERLAYFS")) {
310  		shouldUse = false;
311  		explicitlySet = true;
312  	} else if (getenv("DARLING_NOOVERLAYFS")) {
313  		shouldUse = true;
314  		explicitlySet = true;
315  	}
316  
317  	// https://github.com/microsoft/WSL/issues/8748
318  	// Microsoft is being dumb with its overlayfs implementation and they don't seem to be willing to be fix WSL1-related bugs.
319  	// We therefore have to enable this hack on WSL1.
320  	if (!explicitlySet && isOnWsl1()) {
321  		shouldUse = false;
322  	}
323  
324  	return shouldUse;
325  }
326  
327  static int compareTimespec(const timespec& a, const timespec& b) {
328  	if (a.tv_sec != b.tv_sec) {
329  		return (a.tv_sec > b.tv_sec) ? 1 : -1;
330  	} else if (a.tv_nsec != b.tv_nsec) {
331  		return (a.tv_nsec > b.tv_nsec) ? 1 : -1;
332  	} else {
333  		return 0;
334  	}
335  }
336  
337  static void copyAndSetAttributes(std::string& fromPath, std::string& toPath) {
338  	struct stat fromStat, toStat;
339  	if (lstat(fromPath.c_str(), &fromStat) == -1) {
340  		fprintf(stderr, "Failed to stat file %s: %s\n", fromPath.c_str(), strerror(errno));
341  		abort();
342  	}
343  	bool destinationExists = true;
344  	bool updateAttributes = false;
345  	if (lstat(toPath.c_str(), &toStat) == -1) {
346  		if (errno != ENOENT) {
347  			fprintf(stderr, "Failed to stat file %s: %s\n", toPath.c_str(), strerror(errno));
348  			abort();
349  		}
350  		destinationExists = false;
351  	}
352  
353  	if (S_ISDIR(fromStat.st_mode)) {
354  		if (destinationExists && !S_ISDIR(toStat.st_mode)) {
355  			return;
356  		} else {
357  			if (!destinationExists) {
358  				if (mkdir(toPath.c_str(), fromStat.st_mode & ALLPERMS) == -1) {
359  					fprintf(stderr, "Failed to create directory %s: %s\n", toPath.c_str(), strerror(errno));
360  					abort();
361  				}
362  				updateAttributes = true;
363  			}
364  			DIR* fromDir = opendir(fromPath.c_str());
365  			if (fromDir == NULL) {
366  				fprintf(stderr, "Failed to open directory %s: %s\n", fromPath.c_str(), strerror(errno));
367  				abort();
368  			}
369  
370  			struct dirent* entry = NULL;
371  			while ((errno = 0) || ((entry = readdir(fromDir)) != NULL)) {
372  				if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
373  					continue;
374  				}
375  
376  				size_t oldFromSize = fromPath.size();
377  				size_t oldToSize = toPath.size();
378  
379  				fromPath.push_back('/');
380  				toPath.push_back('/');
381  
382  				fromPath.append(entry->d_name);
383  				toPath.append(entry->d_name);
384  
385  				copyAndSetAttributes(fromPath, toPath);
386  
387  				fromPath.resize(oldFromSize);
388  				toPath.resize(oldToSize);
389  			}
390  
391  			if (errno) {
392  				fprintf(stderr, "Failed to read directory %s: %s\n", fromPath.c_str(), strerror(errno));
393  				abort();
394  			}
395  
396  			if (closedir(fromDir) == -1) {
397  				fprintf(stderr, "Failed to close directory %s: %s\n", fromPath.c_str(), strerror(errno));
398  			}
399  		}
400  	} else {
401  		if (destinationExists) {
402  			if ((fromStat.st_mode & S_IFMT) != (toStat.st_mode & S_IFMT)) {
403  				return;
404  			} else {
405  				int compareResult = compareTimespec(fromStat.st_mtim, toStat.st_mtim);
406  				// the lower file is older, don't touch the newer file.
407  				if (compareResult == -1) {
408  					return;
409  				// the lower file and the newer files should be the same.
410  				// we still update the attributes though, in case ownership or permission changes.
411  				} else if (compareResult == 0) {
412  					updateAttributes = true;
413  				} else if (compareResult == 1) {
414  					if (unlink(toPath.c_str()) == -1) {
415  						fprintf(stderr, "Failed to delete old destination file %s: %s\n", toPath.c_str(), strerror(errno));
416  						abort();
417  					}
418  					std::filesystem::copy(fromPath, toPath, std::filesystem::copy_options::copy_symlinks);
419  					updateAttributes = true;
420  				}
421  			}
422  		} else {
423  			std::filesystem::copy(fromPath, toPath, std::filesystem::copy_options::copy_symlinks);
424  			updateAttributes = true;
425  		}
426  	}
427  
428  	if (updateAttributes) {
429  		struct timespec times[] = {
430  			fromStat.st_atim,
431  			fromStat.st_mtim
432  		};
433  		if (utimensat(-1, toPath.c_str(), times, AT_SYMLINK_NOFOLLOW) == -1) {
434  			fprintf(stderr, "Failed to set timestamp for %s: %s\n", toPath.c_str(), strerror(errno));
435  			abort();
436      	}
437  		if (fchownat(-1, toPath.c_str(), fromStat.st_uid, fromStat.st_gid, AT_SYMLINK_NOFOLLOW) == -1) {
438  			fprintf(stderr, "Failed to set owner for %s: %s\n", toPath.c_str(), strerror(errno));
439  			abort();
440  		}
441  		// POSIX said that AT_SYMLINK_NOFOLLOW is acceptable for links, but on Linux all calls with AT_SYMLINK_NOFOLLOW fails with ENOTSUP.
442  		if (fchmodat(-1, toPath.c_str(), fromStat.st_mode & ALLPERMS, S_ISLNK(fromStat.st_mode) ? AT_SYMLINK_NOFOLLOW : 0) == -1) {
443  			if (!(S_ISLNK(fromStat.st_mode) && (errno == ENOTSUP))) {
444  				fprintf(stderr, "Failed to set permissions for %s: %s\n", toPath.c_str(), strerror(errno));
445  				abort();
446  			}
447  		}
448  	}
449  }
450  
451  static void temp_drop_privileges(uid_t uid, gid_t gid) {
452  	// it's important to drop GID first, because non-root users can't change their GID
453  	if (setresgid(gid, gid, 0) < 0) {
454  		fprintf(stderr, "Failed to temporarily drop group privileges\n");
455  		exit(1);
456  	}
457  	if (setresuid(uid, uid, 0) < 0) {
458  		fprintf(stderr, "Failed to temporarily drop user privileges\n");
459  		exit(1);
460  	}
461  };
462  
463  static void perma_drop_privileges(uid_t uid, gid_t gid) {
464  	if (setresgid(gid, gid, gid) < 0) {
465  		fprintf(stderr, "Failed to drop group privileges\n");
466  		exit(1);
467  	}
468  	if (setresuid(uid, uid, uid) < 0) {
469  		fprintf(stderr, "Failed to drop user privileges\n");
470  		exit(1);
471  	}
472  };
473  
474  static void regain_privileges() {
475  	if (seteuid(0) < 0) {
476  		fprintf(stderr, "Failed to regain root EUID\n");
477  		exit(1);
478  	}
479  	if (setegid(0) < 0) {
480  		fprintf(stderr, "Failed to regain root EGID\n");
481  		exit(1);
482  	}
483  
484  	if (setresuid(0, 0, 0) < 0) {
485  		fprintf(stderr, "Failed to regain root privileges\n");
486  		exit(1);
487  	}
488  	if (setresgid(0, 0, 0) < 0) {
489  		fprintf(stderr, "Failed to regain root privileges\n");
490  		exit(1);
491  	}
492  };
493  
494  #if DSERVER_ASAN
495  static void handle_sigusr1(int signum) {
496  	__lsan_do_recoverable_leak_check();
497  };
498  #endif
499  
500  int main(int argc, char** argv) {
501  	const char* prefix = NULL;
502  	uid_t originalUID = -1;
503  	gid_t originalGID = -1;
504  	int pipefd = -1;
505  	bool fix_permissions = false;
506  	pid_t launchdGlobalPID = -1;
507  	size_t prefix_length = 0;
508  	struct rlimit default_limit;
509  	struct rlimit increased_limit;
510  	FILE* nr_open_file = NULL;
511  	int childWaitFDs[2];
512  	struct rlimit core_limit;
513  
514  	char *opts;
515  	char putOld[4096];
516  	char *p;
517  
518  	if (argc < 6) {
519  		fprintf(stderr, "darlingserver is not meant to be started manually\n");
520  		exit(1);
521  	}
522  
523  #if DSERVER_EXTENDED_DEBUG
524  	if (getenv("DSERVER_WAIT4DEBUGGER")) {
525  		volatile bool debugged = false;
526  		while (!debugged);
527  	}
528  #endif
529  
530  	prefix = argv[1];
531  	sscanf(argv[2], "%d", &originalUID);
532  	sscanf(argv[3], "%d", &originalGID);
533  	sscanf(argv[4], "%d", &pipefd);
534  
535  	if (argv[5][0] == '1') {
536  		fix_permissions = true;
537  	}
538  
539  	prefix_length = strlen(prefix);
540  
541  	if (getuid() != 0 || getgid() != 0) {
542  		fprintf(stderr, "darlingserver needs to start as root\n");
543  		exit(1);
544  	}
545  
546  	// temporarily drop privileges to perform some prefix work
547  	temp_drop_privileges(originalUID, originalGID);
548  	setupUserHome(prefix, originalUID);
549  	//setupCoredumpPattern();
550  	regain_privileges();
551  
552  	// read the default rlimit so we can restore it for our children
553  	if (getrlimit(RLIMIT_NOFILE, &default_limit) != 0) {
554  		fprintf(stderr, "Failed to read default FD rlimit: %s\n", strerror(errno));
555  		//exit(1);
556  	} else {
557  		// ASAN uses a rather obtuse way of closing descriptors for child processes it spawns,
558  		// and increasing the FD limit screws with it (it tries to close every single descriptor up to the FD limit).
559  #if !DSERVER_ASAN
560  		// read the system maximum
561  		nr_open_file = fopen("/proc/sys/fs/nr_open", "r");
562  		if (nr_open_file == NULL) {
563  			fprintf(stderr, "Warning: failed to open /proc/sys/fs/nr_open: %s\n", strerror(errno));
564  			increased_limit.rlim_cur = increased_limit.rlim_max = default_limit.rlim_max;
565  			//exit(1);
566  		} else {
567  			if (fscanf(nr_open_file, "%lu", &increased_limit.rlim_max) != 1) {
568  				fprintf(stderr, "Failed to read /proc/sys/fs/nr_open: %s\n", strerror(errno));
569  				exit(1);
570  			}
571  			increased_limit.rlim_cur = increased_limit.rlim_max;
572  			if (fclose(nr_open_file) != 0) {
573  				fprintf(stderr, "Failed to close /proc/sys/fs/nr_open: %s\n", strerror(errno));
574  				exit(1);
575  			}
576  		}
577  
578  		// now set our increased rlimit
579  		if (setrlimit(RLIMIT_NOFILE, &increased_limit) != 0) {
580  			fprintf(stderr, "Warning: failed to increase FD rlimit: %s\n", strerror(errno));
581  			//exit(1);
582  		}
583  
584  #endif
585  	}
586  
587  	if (getrlimit(RLIMIT_CORE, &core_limit) != 0) {
588  		fprintf(stderr, "Failed to read default core rlimit: %s\n", strerror(errno));
589  	} else {
590  		// increase the core limit to the maximum
591  		core_limit.rlim_cur = core_limit.rlim_max;
592  
593  		if (setrlimit(RLIMIT_CORE, &core_limit) != 0) {
594  			fprintf(stderr, "Warning: failed to increase core limit: %s\n", strerror(errno));
595  		}
596  	}
597  
598  	// Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace
599  	// and do the mount while we can be root
600  	if (unshare(CLONE_NEWNS) != 0)
601  	{
602  		fprintf(stderr, "Cannot unshare PID and mount namespaces: %s\n", strerror(errno));
603  		exit(1);
604  	}
605  
606  	int shmMountFlags = MS_NOSUID | MS_NODEV;
607  	// Workaround for dumb Microsoft bug: https://github.com/microsoft/WSL/issues/8777
608  	if (!isOnWsl1())
609  	{
610  		shmMountFlags |= MS_NOEXEC;
611  	}
612  
613  	umount("/dev/shm");
614  	if (mount("tmpfs", "/dev/shm", "tmpfs", shmMountFlags, NULL) != 0)
615  	{
616  		fprintf(stderr, "Cannot mount new /dev/shm: %s\n", strerror(errno));
617  		exit(1);
618  	}
619  
620  	if (shouldUseOverlayFs()) {
621  		// Because systemd marks / as MS_SHARED and we would inherit this into the overlay mount,
622  		// causing it not to be unmounted once the init process dies.
623  		if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) != 0)
624  		{
625  			fprintf(stderr, "Cannot remount / as slave: %s\n", strerror(errno));
626  			exit(1);
627  		}
628  
629  		opts = (char*) malloc(strlen(prefix)*2 + sizeof(LIBEXEC_PATH) + 100);
630  
631  		const char* opts_fmt = "lowerdir=%s,upperdir=%s,workdir=%s.workdir,index=off";
632  
633  		sprintf(opts, opts_fmt, LIBEXEC_PATH, prefix, prefix);
634  
635  		// Mount overlay onto our prefix
636  		if (mount("overlay", prefix, "overlay", 0, opts) != 0)
637  		{
638  			if (errno == EINVAL) {
639  				opts_fmt = "lowerdir=%s,upperdir=%s,workdir=%s.workdir";
640  				sprintf(opts, opts_fmt, LIBEXEC_PATH, prefix, prefix);
641  				if (mount("overlay", prefix, "overlay", 0, opts) == 0) {
642  					goto mount_ok;
643  				}
644  			}
645  			fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno));
646  			exit(1);
647  		}
648  
649  	mount_ok:
650  		free(opts);
651  	} else {
652  		std::string fromPath = LIBEXEC_PATH;
653  		std::string toPath = prefix;
654  		copyAndSetAttributes(fromPath, toPath);
655  	}
656  
657  	// This is executed once at prefix creation
658  	if (fix_permissions) {
659  		const char* extra_paths[] = {
660  			"/private/etc/passwd",
661  			"/private/etc/master.passwd",
662  			"/private/etc/group",
663  		};
664  		char path[4096];
665  
666  		fixPermissionsRecursive(prefix, originalUID, originalGID);
667  
668  		path[sizeof(path) - 1] = '\0';
669  		strncpy(path, prefix, sizeof(path) - 1);
670  		for (size_t i = 0; i < sizeof(extra_paths) / sizeof(*extra_paths); ++i) {
671  			path[prefix_length] = '\0';
672  			strncat(path, extra_paths[i], sizeof(path) - 1);
673  			fixPermissionsRecursive(path, originalUID, originalGID);
674  		}
675  	}
676  
677  	// temporarily drop privileges and do some prefix work
678  	temp_drop_privileges(originalUID, originalGID);
679  	darlingPreInit(prefix);
680  	regain_privileges();
681  
682  	// Tell the parent we're ready
683  	write(pipefd, ".", 1);
684  	close(pipefd);
685  
686  	if (pipe(childWaitFDs) != 0) {
687  		std::cerr << "Failed to create child waiting pipe: " << strerror(errno) << std::endl;
688  		exit(1);
689  	}
690  
691  	// we have to use `clone` rather than `fork` to create the process in its own PID namespace
692  	// and still be able to spawn new processes and threads of our own
693  	launchdGlobalPID = syscall(SYS_clone, CLONE_NEWPID | SIGCHLD, NULL, NULL, NULL, 0);
694  
695  	if (launchdGlobalPID < 0) {
696  		fprintf(stderr, "Failed to fork to start launchd: %s\n", strerror(errno));
697  		exit(1);
698  	} else if (launchdGlobalPID == 0) {
699  		// this is the child
700  		char buf[1];
701  
702  		close(childWaitFDs[1]);
703  
704  		snprintf(putOld, sizeof(putOld), "%s/proc", prefix);
705  
706  		// mount procfs for our new PID namespace
707  		if (mount("proc", putOld, "proc", 0, "") != 0)
708  		{
709  			fprintf(stderr, "Cannot mount procfs: %s\n", strerror(errno));
710  			exit(1);
711  		}
712  
713  		// drop our privileges now
714  		perma_drop_privileges(originalUID, originalGID);
715  		prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
716  
717  		// decrease the FD limit back to the default
718  		if (setrlimit(RLIMIT_NOFILE, &default_limit) != 0) {
719  			fprintf(stderr, "Warning: failed to decrease FD limit back down for launchd: %s\n", strerror(errno));
720  			//exit(1);
721  		}
722  
723  		// wait for the parent to give us the green light
724  		read(childWaitFDs[0], buf, 1);
725  		close(childWaitFDs[0]);
726  
727  		spawnLaunchd(prefix);
728  		__builtin_unreachable();
729  	}
730  
731  	// this is the parent
732  	close(childWaitFDs[0]);
733  
734  	// drop our privileges
735  	perma_drop_privileges(originalUID, originalGID);
736  	prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
737  
738  #if DSERVER_ASAN
739  	// set up a signal handler to print leak info
740  	struct sigaction leak_info_action;
741  	leak_info_action.sa_handler = handle_sigusr1;
742  	leak_info_action.sa_flags = SA_RESTART;
743  	sigemptyset(&leak_info_action.sa_mask);
744  	sigaction(SIGUSR1, &leak_info_action, NULL);
745  #endif
746  
747  	// create the server
748  	auto server = new DarlingServer::Server(prefix);
749  
750  	// tell the child to go ahead; the socket has been created
751  	write(childWaitFDs[1], ".", 1);
752  	close(childWaitFDs[1]);
753  
754  	// start the main loop
755  	server->start();
756  
757  	// this should never happen
758  	std::cerr << "Server exited main loop!" << std::endl;
759  	delete server;
760  
761  	return 1;
762  };