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>
42 #include <sys/resource.h>
43 #include <orbit/orb-core/corba-defs.h>
44 #include "captive/client.h"
46 #ifdef HAVE_ORBIT_LINK
47 char *link_get_tmpdir(void);
48 void link_set_tmpdir(const char *dir);
50 #include <linc/linc-protocol.h> /* for linc_set_tmpdir() */
54 /* Do not: #include "../../libcaptive/sandbox/split.h" * for captive_sandbox_fd_closeup(); FIXME *
55 * as it has libcaptive-dependent includes conditioned by ORBIT2.
56 * FIXME: Unify this declaration:
58 void captive_sandbox_fd_closeup(int fd_first_to_delete);
59 void captive_corba_sandbox_child(const gchar *chrooted_orbit_dir);
64 /* FIXME: We hit linc-1.0.1/src/linc-protocols.c/linc_protocol_get_sockaddr_unix()
65 * limit of socket pathname 64 characters.
66 * With CHROOT_PATH_HASHKEY_LENGTH 12 "linc-%x-%x-%x%x" still does not fit completely.
68 #define CHROOT_PATH_HASHKEY_LENGTH (12)
71 GQuark sandbox_server_main_error_quark(void)
76 r=g_quark_from_static_string("sandbox-server");
82 static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
83 static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
84 static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
85 static gint optarg_no_rlimit=0;
87 static const struct poptOption popt_table[]={
88 #define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
90 longName: (longname), \
92 argInfo: (argInfoP), \
95 descrip: (descripP), \
96 argDescrip: (argDescripP), \
99 SANDBOX_SERVER_POPT("setuid" ,POPT_ARG_STRING,&optarg_setuid,
100 N_("Username or UID to become; \"-\" for disable"),N_("UID")),
101 SANDBOX_SERVER_POPT("setgid" ,POPT_ARG_STRING,&optarg_setgid,
102 N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
103 SANDBOX_SERVER_POPT("chroot" ,POPT_ARG_STRING,&optarg_chroot,
104 N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
105 SANDBOX_SERVER_POPT("no-rlimit",POPT_ARG_NONE ,&optarg_no_rlimit,
106 N_("Disable setrlimit(2) restrictions"),NULL),
108 #undef SANDBOX_SERVER_POPT
114 static gchar *fatal_argv0;
116 static void fatal(const char *fmt,...)
120 fprintf(stderr,"%s: ",fatal_argv0);
122 vfprintf(stderr,fmt,ap);
124 fprintf(stderr,"!\nAborting!\n");
130 static void check_dir_safety(const gchar *dir)
137 fatal("Loop count >=%d during check_dir_safety(\"%s\")",depth,dir);
140 fatal("chroot path \"%s\" not absolute",dir);
141 dir=captive_printf_alloca("%s/",dir);
142 local_dir=(gchar *)captive_strdup_alloca(dir);
143 for (cs=dir;cs;cs=strchr(cs+1,'/')) {
147 /* Include the trailing '/' to resolve the root directory as "/". */
148 memcpy(local_dir,dir,cs+1-dir);
149 local_dir[cs+1-dir]=0;
150 if (lstat(local_dir,&statbuf))
151 fatal("lstat(\"%s\") of chroot path component: %m",local_dir);
152 if (S_ISLNK(statbuf.st_mode)) {
153 char linkbuf[PATH_MAX];
156 if (0>(linkbuflen=readlink(local_dir,linkbuf,sizeof(linkbuf)-1)))
157 fatal("readlink(\"%s\") of chroot path component: %m",local_dir);
158 linkbuf[linkbuflen]=0;
159 check_dir_safety(linkbuf);
160 if (stat(local_dir,&statbuf)) /* NOT lstat(2) */
161 fatal("stat(\"%s\") of chroot path component: %m",local_dir);
163 if (!S_ISDIR(statbuf.st_mode))
164 fatal("lstat/stat(\"%s\") of chroot path component is !S_ISDIR",local_dir);
165 if (statbuf.st_uid!=0)
166 fatal("lstat/stat(\"%s\") of chroot path component has UID %d !=0",local_dir,(int)statbuf.st_uid);
167 if (statbuf.st_gid!=0)
168 fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0",local_dir,(int)statbuf.st_gid);
169 if ((statbuf.st_mode&(S_IFDIR|S_ISVTX|0111)) != (S_IFDIR|0111))
170 fatal("lstat/stat(\"%s\") of chroot path component has mode 0%o !=04[01]111",local_dir,(int)statbuf.st_mode);
176 static void chrooted_unlink_recursive(const gchar *pathname)
179 struct dirent *dirent;
184 fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
186 /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
187 if (lstat(pathname,&statbuf))
188 fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
189 if (!S_ISDIR(statbuf.st_mode)) {
190 if (unlink(pathname))
191 fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
194 if (!(dir=opendir(pathname)))
195 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
196 while (errno=0,(dirent=readdir(dir))) {
199 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
201 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
202 chrooted_unlink_recursive(dirent_path);
206 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
208 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
210 fatal("Cannot rmdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
216 static void chrooted_cleanuplockeddirs(const gchar *pathname,const gchar *prefix)
219 struct dirent *dirent;
221 if (!(dir=opendir(pathname))) {
223 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
224 /* errno==ENOTDIR, a regular file */
225 if (unlink(pathname))
226 fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
229 while (errno=0,(dirent=readdir(dir))) {
233 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
235 if (strncmp(dirent->d_name,prefix,strlen(prefix)))
237 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
238 if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
239 if (errno==ENOENT) /* It could disappear in the meantime. */
240 goto next_dirent_free_dirent_path;
241 fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
243 if (flock(direntfd,LOCK_EX|LOCK_NB)) {
244 if (errno==EWOULDBLOCK) /* Valid directory in use. */
245 goto next_dirent_close_direntfd;
246 fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
248 chrooted_unlink_recursive(dirent_path);
249 next_dirent_close_direntfd:
251 fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
252 next_dirent_free_dirent_path:
256 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
258 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
261 static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
265 for (retries=0;retries<10;retries++) {
269 if (mkdir(dir,0711)) {
271 fatal("Failed to create chroot directory \"%s\": %m",dir);
272 chrooted_unlink_recursive(dir);
274 fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
278 dirfd=open(dir,O_RDONLY);
281 fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
284 /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
285 if (flock(dirfd,LOCK_EX))
286 fatal("Failed to lock created chroot directory \"%s\": %m",dir);
287 if (lstat(dir,&statbuf)) {
289 fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
291 fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
294 /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
297 if (chown(dir,uid,gid))
298 fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
299 if (chmod(dir,0711)) /* Just to be safe after chown(2); should be already done by mkdir(2). */
300 fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
303 static void sandbox_server_rlimit(int resource,const gchar *resource_string,rlim_t rlim_max)
307 rlim.rlim_cur=rlim.rlim_max=rlim_max;
308 if (setrlimit(resource,&rlim))
309 fatal("setrlimit(%s,%d): %m",resource_string,(int)rlim_max);
310 if (getrlimit(resource,&rlim))
311 fatal("getrlimit(%s,%d): %m",resource_string,(int)rlim_max);
312 if (rlim.rlim_cur!=rlim_max || rlim.rlim_max!=rlim_max)
313 fatal("Unsuccessful setrlimit(%s)",resource_string);
316 static void sandbox_server_mkdir_p(const gchar *dirpathname)
318 gchar *pathname=(/* de-const */ gchar *)captive_strdup_alloca(dirpathname);
321 /* Missing mkdir(2) of the last component path is intentional: */
322 for (gs=pathname;(gs2=strchr(gs,'/'));gs=gs2) {
324 if (*pathname && mkdir(pathname,S_ISVTX|0777)) {
326 fatal("Failed to mkdir(\"%s\"): %m",pathname);
332 static const gchar *chrooted_orbit_dir;
334 static void chroot_setup(gboolean fragile)
337 const gchar *want_uid_name=NULL;
340 const gchar *chroot_pid_hashkey_dir=NULL;
343 captive_sandbox_fd_closeup(2 /* STDERR */ +1);
347 #define CLEANEMPTY(var) G_STMT_START { \
348 if ((var) && (!*(var) || *(var)=='-')) \
351 CLEANEMPTY(optarg_setgid);
352 CLEANEMPTY(optarg_setuid);
353 CLEANEMPTY(optarg_chroot);
359 want_gidl=strtol(optarg_setgid,&endptr,10);
360 if (!endptr || !*endptr) {
362 if (want_gidl<=0 || want_gid!=(gid_t)want_gidl)
363 fatal("Numeric setgid not parsable: %s",optarg_setgid);
366 struct group *want_gid_group=NULL;
367 if (!(want_gid_group=getgrnam(optarg_setgid)))
368 fatal("Unable to query setgid group name \"%s\"",optarg_setgid);
369 want_gid=want_gid_group->gr_gid;
375 struct passwd *want_uid_passwd;
376 want_uidl=strtol(optarg_setuid,&endptr,10);
377 if (!endptr || !*endptr) {
379 if (want_uidl<=0 || want_uid!=(gid_t)want_uidl)
380 fatal("Numeric setuid not parsable: %s",optarg_setuid);
383 if (!(want_uid_passwd=getpwnam(optarg_setuid)))
384 fatal("Unable to query setuid user name \"%s\"",optarg_setuid);
385 want_uid=want_uid_passwd->pw_uid;
388 fatal("Unable to detect setuid UID");
389 if (!(want_uid_passwd=getpwuid(want_uid)))
390 fatal("Unable to query name of UID %d",(int)want_uid);
391 want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
394 /* Prevent: GLib-WARNING **: getpwuid_r(): failed due to unknown user id (42)
395 * Try to invoke GLib g_get_any_init() before possible chroot(2) below.
402 /* Pre-resolve "link_get_tmpdir" symbol to prevent its later failed
403 * resolving in chroot(2) mode in Debian dynamic build.
405 #ifdef HAVE_ORBIT_LINK
406 g_free(link_get_tmpdir()); /* returns g_strdup()ed string */
408 g_free(linc_get_tmpdir()); /* returns g_strdup()ed string */
411 if (fragile && !optarg_chroot)
412 fatal("Fragile setuid/root environment but no --chroot set");
414 const gchar *chroot_pid_dir;
416 gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
419 check_dir_safety(optarg_chroot);
420 if (!(grand=g_rand_new())) /* I hope g_rand_new() is security-safe. It looks so. */
421 fatal("Cannot initialize random number generator g_rand_new()");
422 for (s=chroot_hashkey;s<chroot_hashkey+CHROOT_PATH_HASHKEY_LENGTH;s++) {
423 gi=g_rand_int_range(grand,0,10+26+26);
424 /**/ if (gi>=0 && gi<10)
426 else if (gi>=10+0 && gi<10+26)
428 else if (gi>=10+26+0 && gi<10+26+26)
430 else g_assert_not_reached();
434 if (geteuid()==0) { /* Not 'fragile' as we can be native 'root'. */
435 chrooted_cleanuplockeddirs(optarg_chroot,"s-");
436 chrooted_cleanuplockeddirs(captive_printf_alloca("%s/tmp",optarg_chroot),"captive-orbit-");
438 chroot_pid_dir=captive_printf_alloca("%s/s-%d",optarg_chroot,(int)getpid());
439 chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
441 chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
442 chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
444 if (chroot(chroot_pid_hashkey_dir))
445 fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
447 fatal("Failed to chdir(\"%s\"): %m","/");
448 /* Now it is safe to set umask(0000) as we are protected by 'chroot_hashkey'.
449 * We need it to permit our spawning parent to hardlink its sockets to us.
452 if (umask(0000)!=0000)
453 fatal("Failed to set umask(0%o): %m",0000);
454 printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
457 if (fragile && !optarg_setgid)
458 fatal("Fragile setuid/root environment but no --setgid set");
460 if (!want_gid || setgid(want_gid))
461 fatal("Failed to setgid(%d)",(!want_gid ? -1 : (int)want_gid));
462 if (setgroups(1 /* size */,&want_gid))
463 fatal("Failed to setgroups(1,[%d])",(!want_gid ? -1 : (int)want_gid));
465 if (fragile && !optarg_setuid)
466 fatal("Fragile setuid/root environment but no --setuid set");
468 if (!want_uid || setuid(want_uid))
469 fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
472 /* Prepare /t for /t/o-$PID directories for ORBit2
473 * and also for parent's hardlink to its /t/o-$pid directory. */
475 gchar *chrooted_orbit_dir_old;
477 if (mkdir("/t",S_ISVTX|0777)) {
479 fatal("Failed to mkdir(\"%s\"): %m","/t");
481 if (mkdir("/etc",0700))
482 fatal("Failed to mkdir(\"%s\"): %m","/etc");
483 if (want_uid_name && want_uid && want_gid) {
485 if (!(f=fopen("/etc/passwd","w")))
486 fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
487 if (0>fprintf(f,"%s:*:%d:%d:%s:%s:/bin/false\n",want_uid_name,(int)want_uid,(int)want_gid,want_uid_name,optarg_chroot))
488 fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
490 fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
492 g_assert(chroot_pid_hashkey_dir!=NULL);
493 chrooted_orbit_dir=g_strdup_printf("%s/t/o-%d",chroot_pid_hashkey_dir,getpid());
494 /* Last pathname component is not created: */
495 sandbox_server_mkdir_p(chrooted_orbit_dir);
496 /* Prepare '/tmp' for the initial CORBA_ORB_init() default path.
497 * Workaround sandbox_server_mkdir_p() does not create last component.
498 * Do not use '/tmp' directly as some distributions may set custom
499 * tmpdir pathname by $ENV{"TMPDIR"} etc.
501 sandbox_server_mkdir_p(captive_printf_alloca("%s/",g_get_tmp_dir()));
502 /* Set '0700' to prevent: Wrong permissions for ...
503 * by linc-1.0.1-1/src/linc-protocols.c/make_local_tmpdir()
505 if (mkdir(chrooted_orbit_dir,0700)) {
506 /* Do not: g_assert(errno==EEXIST);
507 * as if 'optarg_chroot' the whole chroot(2)ed directory should be ours.
509 fatal("Cannot created chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir);
511 /* Init 'orb' to pass through its linc_set_tmpdir() to not to be overriden below. */
514 CORBA_Environment ev;
517 (gchar *)captive_strdup_alloca("captive-sandbox-server"),
520 CORBA_exception_init(&ev);
521 /* libcaptive is single-threaded only, caller must lock it.
522 * If thread A spawned the sandbox while currently doing its own work
523 * and thread B calls the sandbox thread B waits on ORB_run()
524 * while the sandbox waits for the response of thread A ORB. Deadlock.
525 * "orbit-local-non-threaded-orb" requests thread unaware ORB.
527 orb=CORBA_ORB_init(&orb_argc,orb_argv,"orbit-local-non-threaded-orb",&ev);
528 if (orb==CORBA_OBJECT_NIL)
529 fatal("Cannot initialize CORBA ORB (CORBA_OBJECT_NIL): %m");
530 if (ev._major!=CORBA_NO_EXCEPTION)
531 fatal("Cannot initialize CORBA ORB (exception): %m");
533 #ifdef HAVE_ORBIT_LINK
534 chrooted_orbit_dir_old=link_get_tmpdir(); /* returns g_strdup()ed string */
536 chrooted_orbit_dir_old=linc_get_tmpdir(); /* returns g_strdup()ed string */
538 g_assert(chrooted_orbit_dir_old!=NULL);
539 #ifdef HAVE_ORBIT_LINK
540 link_set_tmpdir(chrooted_orbit_dir);
542 linc_set_tmpdir(chrooted_orbit_dir);
544 if (!*chrooted_orbit_dir_old)
545 fatal("Cannot detect chrooted_orbit_dir: --with-orbit-line incompatible with ORBit2 version");
546 if (rmdir(chrooted_orbit_dir_old))
547 fatal("Cannot remove old chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir_old);
548 g_free(chrooted_orbit_dir_old);
549 /* chmod(2) it to prevent mode limitation by
550 * active ulimit(2) of being executed by mount(8).
552 /* Set '0777' as our parent does not have 'captive' user permissions. */
553 if (chmod(chrooted_orbit_dir,S_ISVTX|0777))
554 fatal("Cannot chmod 0%o chrooted_orbit_dir \"%s\": %m",S_ISVTX|0777,chrooted_orbit_dir);
555 printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
558 if (fragile || !optarg_no_rlimit) {
559 #define SANDBOX_SERVER_RLIMIT(what,how) sandbox_server_rlimit((what),G_STRINGIFY(what),(how))
560 SANDBOX_SERVER_RLIMIT(RLIMIT_NPROC,0);
561 SANDBOX_SERVER_RLIMIT(RLIMIT_MEMLOCK,0);
562 SANDBOX_SERVER_RLIMIT(RLIMIT_CORE,0);
563 SANDBOX_SERVER_RLIMIT(RLIMIT_FSIZE,0);
564 SANDBOX_SERVER_RLIMIT(RLIMIT_NOFILE,16); /* >=6; newer ORBit2/link require >6 */
565 /* FIXME: Why flock(dirfd,...) in chrooted_createdir() succeeds?: */
566 SANDBOX_SERVER_RLIMIT(RLIMIT_LOCKS,0);
567 #undef SANDBOX_SERVER_RLIMIT
574 if (getuid()!=want_uid)
575 fatal("getuid()=%d != want_uid=%d",(int)getuid(),(int)want_uid);
576 if (geteuid()!=want_uid)
577 fatal("geteuid()=%d != want_uid=%d",(int)geteuid(),(int)want_uid);
578 if (getgid()!=want_gid)
579 fatal("getgid()=%d != want_gid=%d",(int)getgid(),(int)want_gid);
580 if (getegid()!=want_gid)
581 fatal("getegid()=%d != want_gid=%d",(int)getegid(),(int)want_gid);
582 gid_list_size=getgroups(G_N_ELEMENTS(gid_list),gid_list);
583 for (i=0;i<gid_list_size;i++) {
584 if (gid_list[i]!=want_gid)
585 fatal("getgroups() list member @%d %d != want_gid=%d",i,(int)gid_list[i],(int)want_gid);
591 int main(int argc,char **argv)
596 struct captive_options options;
599 g_log_set_always_fatal(~(0
606 fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
608 #ifndef MAINTAINER_MODE
609 if (fragile && (argc!=1 || argv[1]))
610 fatal("Arguments invalid as running in fragile setuid/root environment");
614 #endif /* MAINTAINER_MODE */
616 captive_standalone_init();
618 captive_options_init(&options);
619 captive_options=&options; /* for parsing by 'CAPTIVE_POPT_INCLUDE' */
621 context=poptGetContext(
623 argc,(/*en-const*/const char **)argv, /* argc,argv */
624 popt_table, /* options */
625 POPT_CONTEXT_POSIXMEHARDER); /* flags; && !POPT_CONTEXT_KEEP_FIRST */
627 g_assert_not_reached(); /* argument recognization args_error */
630 errint=poptReadDefaultConfig(context,
633 g_assert_not_reached(); /* argument recognization args_error */
636 errint=poptGetNextOpt(context);
638 g_assert_not_reached(); /* some non-callbacked argument reached */
641 cmd_arg=poptPeekArg(context);
643 g_assert_not_reached(); /* some non-option argument reached */
646 /* 'cmd_arg'-style args gets cleared by 'poptFreeContext(context);' below */
647 poptFreeContext(context);
649 #ifdef MAINTAINER_MODE
651 #endif /* MAINTAINER_MODE */
653 captive_options=NULL; /* already parsed by 'CAPTIVE_POPT_INCLUDE' */
655 captive_corba_sandbox_child(chrooted_orbit_dir);
657 g_assert_not_reached();