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 */
45 #define CHROOT_PATH_HASHKEY_LENGTH (64)
48 GQuark sandbox_server_main_error_quark(void)
53 r=g_quark_from_static_string("sandbox-server");
59 static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
60 static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
61 static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
63 static const struct poptOption popt_table[]={
64 #define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
66 longName: (longname), \
68 argInfo: (argInfoP), \
71 descrip: (descripP), \
72 argDescrip: (argDescripP), \
75 SANDBOX_SERVER_POPT("setuid" ,POPT_ARG_STRING,&optarg_setuid,N_("Username or UID to become; \"-\" for disable"),N_("UID")),
76 SANDBOX_SERVER_POPT("setgid" ,POPT_ARG_STRING,&optarg_setgid,N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
77 SANDBOX_SERVER_POPT("chroot" ,POPT_ARG_STRING,&optarg_chroot,N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
79 #undef SANDBOX_SERVER_POPT
85 static gchar *fatal_argv0;
87 static void fatal(const char *fmt,...)
91 fprintf(stderr,"%s: ",fatal_argv0);
93 vfprintf(stderr,fmt,ap);
95 fprintf(stderr,"!\nAborting!\n");
101 static void check_dir_safety(const gchar *dir)
108 fatal("Loop count >=%d during check_dir_safety(\"%s\")",depth,dir);
111 fatal("chroot path \"%s\" not absolute",dir);
112 dir=captive_printf_alloca("%s/",dir);
113 local_dir=(gchar *)captive_strdup_alloca(dir);
114 for (cs=dir;cs;cs=strchr(cs+1,'/')) {
118 /* Include the trailing '/' to resolve the root directory as "/". */
119 memcpy(local_dir,dir,cs+1-dir);
120 local_dir[cs+1-dir]=0;
121 if (lstat(local_dir,&statbuf))
122 fatal("lstat(\"%s\") of chroot path component: %m",local_dir);
123 if (S_ISLNK(statbuf.st_mode)) {
124 char linkbuf[PATH_MAX];
127 if (0>(linkbuflen=readlink(local_dir,linkbuf,sizeof(linkbuf)-1)))
128 fatal("readlink(\"%s\") of chroot path component: %m",local_dir);
129 linkbuf[linkbuflen]=0;
130 check_dir_safety(linkbuf);
131 if (stat(local_dir,&statbuf)) /* NOT lstat(2) */
132 fatal("stat(\"%s\") of chroot path component: %m",local_dir);
134 if (!S_ISDIR(statbuf.st_mode))
135 fatal("lstat/stat(\"%s\") of chroot path component is !S_ISDIR",local_dir);
136 if (statbuf.st_uid!=0)
137 fatal("lstat/stat(\"%s\") of chroot path component has UID %d !=0",local_dir,(int)statbuf.st_uid);
138 if (statbuf.st_gid!=0)
139 fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0",local_dir,(int)statbuf.st_gid);
140 if ((statbuf.st_mode&(S_IFDIR|S_ISVTX|0111)) != (S_IFDIR|0111))
141 fatal("lstat/stat(\"%s\") of chroot path component has mode 0%o !=04[01]111",local_dir,(int)statbuf.st_mode);
147 static void unlink_recursive_suidsafe(const gchar *pathname)
150 struct dirent *dirent;
154 fatal("Loop count >=%d during unlink_recursive_suidsafe(\"%s\")",depth,pathname);
156 if (!(dir=opendir(pathname))) {
158 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
159 /* errno==ENOTDIR, a regular file */
160 if (unlink(pathname))
161 fatal("Cannot pathname(\"%s\") to delete leftover sandbox files: %m",pathname);
164 while (errno=0,(dirent=readdir(dir))) {
167 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
169 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
170 unlink_recursive_suidsafe(dirent_path);
174 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
176 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
178 fatal("Cannot rmdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
184 static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid)
186 if (mkdir(dir,0711)) {
188 fatal("Failed to create chroot directory \"%s\": %m",dir);
189 unlink_recursive_suidsafe(dir);
191 fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
193 if (chown(dir,uid,gid))
194 fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
195 if (chmod(dir,0711)) /* Just to be safe after chown(2); should be already done by mkdir(2). */
196 fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
199 static const gchar *chrooted_orbit_dir;
201 static void chroot_setup(gboolean fragile)
204 const gchar *want_uid_name=NULL;
209 captive_sandbox_fd_closeup(2 /* STDERR */ +1);
213 #define CLEANEMPTY(var) G_STMT_START { \
214 if ((var) && (!*(var) || *(var)=='-')) \
217 CLEANEMPTY(optarg_setgid);
218 CLEANEMPTY(optarg_setuid);
219 CLEANEMPTY(optarg_chroot);
225 want_gidl=strtol(optarg_setgid,&endptr,10);
226 if (!endptr || !*endptr) {
228 if (want_gidl<=0 || want_gid!=(gid_t)want_gidl)
229 fatal("Numeric setgid not parsable: %s",optarg_setgid);
232 struct group *want_gid_group=NULL;
233 if (!(want_gid_group=getgrnam(optarg_setgid)))
234 fatal("Unable to query setgid group name \"%s\"",optarg_setgid);
235 want_gid=want_gid_group->gr_gid;
241 struct passwd *want_uid_passwd;
242 want_uidl=strtol(optarg_setuid,&endptr,10);
243 if (!endptr || !*endptr) {
245 if (want_uidl<=0 || want_uid!=(gid_t)want_uidl)
246 fatal("Numeric setuid not parsable: %s",optarg_setuid);
249 if (!(want_uid_passwd=getpwnam(optarg_setuid)))
250 fatal("Unable to query setuid user name \"%s\"",optarg_setuid);
251 want_uid=want_uid_passwd->pw_uid;
254 fatal("Unable to detect setuid UID");
255 if (!(want_uid_passwd=getpwuid(want_uid)))
256 fatal("Unable to query name of UID %d",(int)want_uid);
257 want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
260 if (fragile && !optarg_chroot)
261 fatal("Fragile setuid/root environment but no --chroot set");
263 const gchar *chroot_pid_dir,*chroot_pid_hashkey_dir;
265 gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
268 check_dir_safety(optarg_chroot);
269 if (!(grand=g_rand_new())) /* I hope g_rand_new() is security-safe. It looks so. */
270 fatal("Cannot initialize random number generator g_rand_new()");
271 for (s=chroot_hashkey;s<chroot_hashkey+CHROOT_PATH_HASHKEY_LENGTH;s++) {
272 gi=g_rand_int_range(grand,0,10+26+26);
273 /**/ if (gi>=0 && gi<10)
275 else if (gi>=10+0 && gi<10+26)
277 else if (gi>=10+26+0 && gi<10+26+26)
279 else g_assert_not_reached();
283 chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
284 chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
285 chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
286 chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
287 if (chroot(chroot_pid_hashkey_dir))
288 fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
290 fatal("Failed to chdir(\"%s\"): %m","/");
291 /* Now it is safe to set umask(0000) as we are protected by 'chroot_hashkey'.
292 * We need it to permit our spawning parent to hardlink its sockets to us.
295 if (umask(0000)!=0000)
296 fatal("Failed to set umask(0%o): %m",0000);
298 printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
299 chrooted_orbit_dir=g_strdup_printf("/tmp/orbit-%s",want_uid_name);
300 printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
304 if (fragile && !optarg_setgid)
305 fatal("Fragile setuid/root environment but no --setgid set");
307 if (!want_gid || setgid(want_gid))
308 fatal("Failed to setgid(%d)",(!want_gid ? -1 : (int)want_gid));
309 if (setgroups(1 /* size */,&want_gid))
310 fatal("Failed to setgroups(1,[%d])",(!want_gid ? -1 : (int)want_gid));
312 if (fragile && !optarg_setuid)
313 fatal("Fragile setuid/root environment but no --setuid set");
315 if (!want_uid || setuid(want_uid))
316 fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
319 /* Prepare /tmp for /tmp/orbit-$username directories for ORBit2
320 * and also for parent's hardlink to its /tmp/captive-orbit-$pid directory. */
322 if (mkdir("/tmp",S_ISVTX|0777))
323 fatal("Failed to mkdir(\"%s\"): %m","/tmp");
324 if (mkdir("/etc",0700))
325 fatal("Failed to mkdir(\"%s\"): %m","/etc");
326 if (want_uid_name && want_uid && want_gid) {
328 if (!(f=fopen("/etc/passwd","w")))
329 fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
330 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))
331 fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
333 fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
341 if (getuid()!=want_uid)
342 fatal("getuid()=%d != want_uid=%d",(int)getuid(),(int)want_uid);
343 if (geteuid()!=want_uid)
344 fatal("geteuid()=%d != want_uid=%d",(int)geteuid(),(int)want_uid);
345 if (getgid()!=want_gid)
346 fatal("getgid()=%d != want_gid=%d",(int)getgid(),(int)want_gid);
347 if (getegid()!=want_gid)
348 fatal("getegid()=%d != want_gid=%d",(int)getegid(),(int)want_gid);
349 gid_list_size=getgroups(G_N_ELEMENTS(gid_list),gid_list);
350 for (i=0;i<gid_list_size;i++) {
351 if (gid_list[i]!=want_gid)
352 fatal("getgroups() list member @%d %d != want_gid=%d",i,(int)gid_list[i],(int)want_gid);
358 int main(int argc,char **argv)
363 struct captive_options options;
366 g_log_set_always_fatal(~(0
373 fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
375 #ifndef MAINTAINER_MODE
376 if (fragile && (argc!=1 || argv[1]))
377 fatal("Arguments invalid as running in fragile setuid/root environment");
381 #endif /* MAINTAINER_MODE */
383 /* Initialize the i18n stuff */
384 setlocale(LC_ALL,"");
385 bindtextdomain(PACKAGE,LOCALEDIR);
388 /* Initialize GObject subsystem of GLib. */
391 captive_options_init(&options);
392 captive_options=&options; /* for parsing by 'CAPTIVE_POPT_INCLUDE' */
394 context=poptGetContext(
396 argc,(/*en-const*/const char **)argv, /* argc,argv */
397 popt_table, /* options */
398 POPT_CONTEXT_POSIXMEHARDER); /* flags; && !POPT_CONTEXT_KEEP_FIRST */
400 g_assert_not_reached(); /* argument recognization args_error */
403 errint=poptReadDefaultConfig(context,
406 g_assert_not_reached(); /* argument recognization args_error */
409 errint=poptGetNextOpt(context);
411 g_assert_not_reached(); /* some non-callbacked argument reached */
414 cmd_arg=poptPeekArg(context);
416 g_assert_not_reached(); /* some non-option argument reached */
419 /* 'cmd_arg'-style args gets cleared by 'poptFreeContext(context);' below */
420 poptFreeContext(context);
422 #ifdef MAINTAINER_MODE
424 #endif /* MAINTAINER_MODE */
426 captive_options=NULL; /* already parsed by 'CAPTIVE_POPT_INCLUDE' */
428 captive_corba_sandbox_child(chrooted_orbit_dir);
430 g_assert_not_reached();