2 * filesystem sandbox server stub for libcaptive
3 * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; exactly version 2 of June 1991 is required
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include <glib/gmessages.h>
24 #include <glib/giochannel.h>
25 #include <glib/gerror.h>
30 #include "captive/options.h"
31 #include <glib-object.h>
32 #include "captive/macros.h"
33 #include <sys/types.h>
38 #include "../../libcaptive/sandbox/split.h" /* for captive_sandbox_fd_closeup(); FIXME */
47 #define CHROOT_PATH_HASHKEY_LENGTH (64)
50 GQuark sandbox_server_main_error_quark(void)
55 r=g_quark_from_static_string("sandbox-server");
61 static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
62 static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
63 static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
65 static const struct poptOption popt_table[]={
66 #define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
68 longName: (longname), \
70 argInfo: (argInfoP), \
73 descrip: (descripP), \
74 argDescrip: (argDescripP), \
77 SANDBOX_SERVER_POPT("setuid" ,POPT_ARG_STRING,&optarg_setuid,N_("Username or UID to become; \"-\" for disable"),N_("UID")),
78 SANDBOX_SERVER_POPT("setgid" ,POPT_ARG_STRING,&optarg_setgid,N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
79 SANDBOX_SERVER_POPT("chroot" ,POPT_ARG_STRING,&optarg_chroot,N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
81 #undef SANDBOX_SERVER_POPT
87 static gchar *fatal_argv0;
89 static void fatal(const char *fmt,...)
93 fprintf(stderr,"%s: ",fatal_argv0);
95 vfprintf(stderr,fmt,ap);
97 fprintf(stderr,"!\nAborting!\n");
103 static void check_dir_safety(const gchar *dir)
110 fatal("Loop count >=%d during check_dir_safety(\"%s\")",depth,dir);
113 fatal("chroot path \"%s\" not absolute",dir);
114 dir=captive_printf_alloca("%s/",dir);
115 local_dir=(gchar *)captive_strdup_alloca(dir);
116 for (cs=dir;cs;cs=strchr(cs+1,'/')) {
120 /* Include the trailing '/' to resolve the root directory as "/". */
121 memcpy(local_dir,dir,cs+1-dir);
122 local_dir[cs+1-dir]=0;
123 if (lstat(local_dir,&statbuf))
124 fatal("lstat(\"%s\") of chroot path component: %m",local_dir);
125 if (S_ISLNK(statbuf.st_mode)) {
126 char linkbuf[PATH_MAX];
129 if (0>(linkbuflen=readlink(local_dir,linkbuf,sizeof(linkbuf)-1)))
130 fatal("readlink(\"%s\") of chroot path component: %m",local_dir);
131 linkbuf[linkbuflen]=0;
132 check_dir_safety(linkbuf);
133 if (stat(local_dir,&statbuf)) /* NOT lstat(2) */
134 fatal("stat(\"%s\") of chroot path component: %m",local_dir);
136 if (!S_ISDIR(statbuf.st_mode))
137 fatal("lstat/stat(\"%s\") of chroot path component is !S_ISDIR",local_dir);
138 if (statbuf.st_uid!=0)
139 fatal("lstat/stat(\"%s\") of chroot path component has UID %d !=0",local_dir,(int)statbuf.st_uid);
140 if (statbuf.st_gid!=0)
141 fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0",local_dir,(int)statbuf.st_gid);
142 if ((statbuf.st_mode&(S_IFDIR|S_ISVTX|0111)) != (S_IFDIR|0111))
143 fatal("lstat/stat(\"%s\") of chroot path component has mode 0%o !=04[01]111",local_dir,(int)statbuf.st_mode);
149 static void chrooted_unlink_recursive(const gchar *pathname)
152 struct dirent *dirent;
157 fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
159 /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
160 if (lstat(pathname,&statbuf))
161 fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
162 if (!S_ISDIR(statbuf.st_mode)) {
163 if (unlink(pathname))
164 fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
167 if (!(dir=opendir(pathname)))
168 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
169 while (errno=0,(dirent=readdir(dir))) {
172 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
174 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
175 chrooted_unlink_recursive(dirent_path);
179 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
181 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
183 fatal("Cannot rmdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
189 static void chrooted_cleanuplockeddirs(const gchar *pathname)
192 struct dirent *dirent;
194 if (!(dir=opendir(pathname))) {
196 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
197 /* errno==ENOTDIR, a regular file */
198 if (unlink(pathname))
199 fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
202 while (errno=0,(dirent=readdir(dir))) {
206 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
208 if (strncmp(dirent->d_name,"sandbox-server-",strlen("sandbox-server-")))
210 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
211 if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
212 if (errno==ENOENT) /* It could disappear in the meantime. */
213 goto next_dirent_free_dirent_path;
214 fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
216 if (flock(direntfd,LOCK_EX|LOCK_NB)) {
217 if (errno==EWOULDBLOCK) /* Valid directory in use. */
218 goto next_dirent_close_direntfd;
219 fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
221 chrooted_unlink_recursive(dirent_path);
222 next_dirent_close_direntfd:
224 fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
225 next_dirent_free_dirent_path:
229 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
231 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
234 static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
238 for (retries=0;retries<10;retries++) {
242 if (mkdir(dir,0711)) {
244 fatal("Failed to create chroot directory \"%s\": %m",dir);
245 chrooted_unlink_recursive(dir);
247 fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
251 dirfd=open(dir,O_RDONLY);
254 fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
257 /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
258 if (flock(dirfd,LOCK_EX))
259 fatal("Failed to lock created chroot directory \"%s\": %m",dir);
260 if (lstat(dir,&statbuf)) {
262 fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
264 fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
267 /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
270 if (chown(dir,uid,gid))
271 fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
272 if (chmod(dir,0711)) /* Just to be safe after chown(2); should be already done by mkdir(2). */
273 fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
276 static const gchar *chrooted_orbit_dir;
278 static void chroot_setup(gboolean fragile)
281 const gchar *want_uid_name=NULL;
286 captive_sandbox_fd_closeup(2 /* STDERR */ +1);
290 #define CLEANEMPTY(var) G_STMT_START { \
291 if ((var) && (!*(var) || *(var)=='-')) \
294 CLEANEMPTY(optarg_setgid);
295 CLEANEMPTY(optarg_setuid);
296 CLEANEMPTY(optarg_chroot);
302 want_gidl=strtol(optarg_setgid,&endptr,10);
303 if (!endptr || !*endptr) {
305 if (want_gidl<=0 || want_gid!=(gid_t)want_gidl)
306 fatal("Numeric setgid not parsable: %s",optarg_setgid);
309 struct group *want_gid_group=NULL;
310 if (!(want_gid_group=getgrnam(optarg_setgid)))
311 fatal("Unable to query setgid group name \"%s\"",optarg_setgid);
312 want_gid=want_gid_group->gr_gid;
318 struct passwd *want_uid_passwd;
319 want_uidl=strtol(optarg_setuid,&endptr,10);
320 if (!endptr || !*endptr) {
322 if (want_uidl<=0 || want_uid!=(gid_t)want_uidl)
323 fatal("Numeric setuid not parsable: %s",optarg_setuid);
326 if (!(want_uid_passwd=getpwnam(optarg_setuid)))
327 fatal("Unable to query setuid user name \"%s\"",optarg_setuid);
328 want_uid=want_uid_passwd->pw_uid;
331 fatal("Unable to detect setuid UID");
332 if (!(want_uid_passwd=getpwuid(want_uid)))
333 fatal("Unable to query name of UID %d",(int)want_uid);
334 want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
337 if (fragile && !optarg_chroot)
338 fatal("Fragile setuid/root environment but no --chroot set");
340 const gchar *chroot_pid_dir,*chroot_pid_hashkey_dir;
342 gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
345 check_dir_safety(optarg_chroot);
346 if (!(grand=g_rand_new())) /* I hope g_rand_new() is security-safe. It looks so. */
347 fatal("Cannot initialize random number generator g_rand_new()");
348 for (s=chroot_hashkey;s<chroot_hashkey+CHROOT_PATH_HASHKEY_LENGTH;s++) {
349 gi=g_rand_int_range(grand,0,10+26+26);
350 /**/ if (gi>=0 && gi<10)
352 else if (gi>=10+0 && gi<10+26)
354 else if (gi>=10+26+0 && gi<10+26+26)
356 else g_assert_not_reached();
360 if (geteuid()==0) /* Not 'fragile' as we can be native 'root'. */
361 chrooted_cleanuplockeddirs(optarg_chroot);
362 chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
363 chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
365 chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
366 chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
368 if (chroot(chroot_pid_hashkey_dir))
369 fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
371 fatal("Failed to chdir(\"%s\"): %m","/");
372 /* Now it is safe to set umask(0000) as we are protected by 'chroot_hashkey'.
373 * We need it to permit our spawning parent to hardlink its sockets to us.
376 if (umask(0000)!=0000)
377 fatal("Failed to set umask(0%o): %m",0000);
379 printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
380 chrooted_orbit_dir=g_strdup_printf("/tmp/orbit-%s",want_uid_name);
381 printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
385 if (fragile && !optarg_setgid)
386 fatal("Fragile setuid/root environment but no --setgid set");
388 if (!want_gid || setgid(want_gid))
389 fatal("Failed to setgid(%d)",(!want_gid ? -1 : (int)want_gid));
390 if (setgroups(1 /* size */,&want_gid))
391 fatal("Failed to setgroups(1,[%d])",(!want_gid ? -1 : (int)want_gid));
393 if (fragile && !optarg_setuid)
394 fatal("Fragile setuid/root environment but no --setuid set");
396 if (!want_uid || setuid(want_uid))
397 fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
400 /* Prepare /tmp for /tmp/orbit-$username directories for ORBit2
401 * and also for parent's hardlink to its /tmp/captive-orbit-$pid directory. */
403 if (mkdir("/tmp",S_ISVTX|0777))
404 fatal("Failed to mkdir(\"%s\"): %m","/tmp");
405 if (mkdir("/etc",0700))
406 fatal("Failed to mkdir(\"%s\"): %m","/etc");
407 if (want_uid_name && want_uid && want_gid) {
409 if (!(f=fopen("/etc/passwd","w")))
410 fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
411 if (0>fprintf(f,"%s:*:%d:%d:%s:%s:/bin/false",want_uid_name,(int)want_uid,(int)want_gid,want_uid_name,optarg_chroot))
412 fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
414 fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
422 if (getuid()!=want_uid)
423 fatal("getuid()=%d != want_uid=%d",(int)getuid(),(int)want_uid);
424 if (geteuid()!=want_uid)
425 fatal("geteuid()=%d != want_uid=%d",(int)geteuid(),(int)want_uid);
426 if (getgid()!=want_gid)
427 fatal("getgid()=%d != want_gid=%d",(int)getgid(),(int)want_gid);
428 if (getegid()!=want_gid)
429 fatal("getegid()=%d != want_gid=%d",(int)getegid(),(int)want_gid);
430 gid_list_size=getgroups(G_N_ELEMENTS(gid_list),gid_list);
431 for (i=0;i<gid_list_size;i++) {
432 if (gid_list[i]!=want_gid)
433 fatal("getgroups() list member @%d %d != want_gid=%d",i,(int)gid_list[i],(int)want_gid);
439 int main(int argc,char **argv)
444 struct captive_options options;
447 g_log_set_always_fatal(~(0
454 fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
456 #ifndef MAINTAINER_MODE
457 if (fragile && (argc!=1 || argv[1]))
458 fatal("Arguments invalid as running in fragile setuid/root environment");
462 #endif /* MAINTAINER_MODE */
464 /* Initialize the i18n stuff */
465 setlocale(LC_ALL,"");
466 bindtextdomain(PACKAGE,LOCALEDIR);
469 /* Initialize GObject subsystem of GLib. */
472 captive_options_init(&options);
473 captive_options=&options; /* for parsing by 'CAPTIVE_POPT_INCLUDE' */
475 context=poptGetContext(
477 argc,(/*en-const*/const char **)argv, /* argc,argv */
478 popt_table, /* options */
479 POPT_CONTEXT_POSIXMEHARDER); /* flags; && !POPT_CONTEXT_KEEP_FIRST */
481 g_assert_not_reached(); /* argument recognization args_error */
484 errint=poptReadDefaultConfig(context,
487 g_assert_not_reached(); /* argument recognization args_error */
490 errint=poptGetNextOpt(context);
492 g_assert_not_reached(); /* some non-callbacked argument reached */
495 cmd_arg=poptPeekArg(context);
497 g_assert_not_reached(); /* some non-option argument reached */
500 /* 'cmd_arg'-style args gets cleared by 'poptFreeContext(context);' below */
501 poptFreeContext(context);
503 #ifdef MAINTAINER_MODE
505 #endif /* MAINTAINER_MODE */
507 captive_options=NULL; /* already parsed by 'CAPTIVE_POPT_INCLUDE' */
509 captive_corba_sandbox_child(chrooted_orbit_dir);
511 g_assert_not_reached();