#include <unistd.h>
#include <dirent.h>
#include <errno.h>
-#include "../../libcaptive/sandbox/split.h" /* for captive_sandbox_fd_closeup(); FIXME */
#include <grp.h>
#include <pwd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/resource.h>
+#include <orbit/orb-core/corba-defs.h>
+#include "captive/client.h"
+
+#ifdef HAVE_ORBIT_LINK
+char *link_get_tmpdir(void);
+void link_set_tmpdir(const char *dir);
+#else
+#include <linc/linc-protocol.h> /* for linc_set_tmpdir() */
+#endif
+
+
+/* Do not: #include "../../libcaptive/sandbox/split.h" * for captive_sandbox_fd_closeup(); FIXME *
+ * as it has libcaptive-dependent includes conditioned by ORBIT2.
+ * FIXME: Unify this declaration:
+ */
+void captive_sandbox_fd_closeup(int fd_first_to_delete);
+void captive_corba_sandbox_child(const gchar *chrooted_orbit_dir);
/* CONFIG: */
-#define CHROOT_PATH_HASHKEY_LENGTH (64)
+/* FIXME: We hit linc-1.0.1/src/linc-protocols.c/linc_protocol_get_sockaddr_unix()
+ * limit of socket pathname 64 characters.
+ * With CHROOT_PATH_HASHKEY_LENGTH 12 "linc-%x-%x-%x%x" still does not fit completely.
+ */
+#define CHROOT_PATH_HASHKEY_LENGTH (12)
GQuark sandbox_server_main_error_quark(void)
static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
+static gint optarg_no_rlimit=0;
static const struct poptOption popt_table[]={
#define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
argDescrip: (argDescripP), \
}
- SANDBOX_SERVER_POPT("setuid" ,POPT_ARG_STRING,&optarg_setuid,N_("Username or UID to become; \"-\" for disable"),N_("UID")),
- SANDBOX_SERVER_POPT("setgid" ,POPT_ARG_STRING,&optarg_setgid,N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
- SANDBOX_SERVER_POPT("chroot" ,POPT_ARG_STRING,&optarg_chroot,N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
+ SANDBOX_SERVER_POPT("setuid" ,POPT_ARG_STRING,&optarg_setuid,
+ N_("Username or UID to become; \"-\" for disable"),N_("UID")),
+ SANDBOX_SERVER_POPT("setgid" ,POPT_ARG_STRING,&optarg_setgid,
+ N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
+ SANDBOX_SERVER_POPT("chroot" ,POPT_ARG_STRING,&optarg_chroot,
+ N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
+ SANDBOX_SERVER_POPT("no-rlimit",POPT_ARG_NONE ,&optarg_no_rlimit,
+ N_("Disable setrlimit(2) restrictions"),NULL),
#undef SANDBOX_SERVER_POPT
POPT_AUTOHELP
depth--;
}
-static void unlink_recursive(const gchar *pathname)
+static void chrooted_unlink_recursive(const gchar *pathname)
{
DIR *dir;
struct dirent *dirent;
static gint depth=0;
+struct stat statbuf;
if (++depth>=1000)
- fatal("Loop count >=%d during unlink_recursive(\"%s\")",depth,pathname);
+ fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
- if (!(dir=opendir(pathname))) {
- if (errno!=ENOTDIR)
- fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
- /* errno==ENOTDIR, a regular file */
+ /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
+ if (lstat(pathname,&statbuf))
+ fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
+ if (!S_ISDIR(statbuf.st_mode)) {
if (unlink(pathname))
- fatal("Cannot pathname(\"%s\") to delete leftover sandbox files: %m",pathname);
+ fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
goto done;
}
+ if (!(dir=opendir(pathname)))
+ fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
while (errno=0,(dirent=readdir(dir))) {
gchar *dirent_path;
if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
continue;
dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
- unlink_recursive(dirent_path);
+ chrooted_unlink_recursive(dirent_path);
g_free(dirent_path);
}
if (errno)
depth--;
}
-static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid)
+static void chrooted_cleanuplockeddirs(const gchar *pathname,const gchar *prefix)
+{
+DIR *dir;
+struct dirent *dirent;
+
+ if (!(dir=opendir(pathname))) {
+ if (errno!=ENOTDIR)
+ fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
+ /* errno==ENOTDIR, a regular file */
+ if (unlink(pathname))
+ fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
+ return;
+ }
+ while (errno=0,(dirent=readdir(dir))) {
+gchar *dirent_path;
+int direntfd;
+
+ if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
+ continue;
+ if (strncmp(dirent->d_name,prefix,strlen(prefix)))
+ continue;
+ dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
+ if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
+ if (errno==ENOENT) /* It could disappear in the meantime. */
+ goto next_dirent_free_dirent_path;
+ fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
+ }
+ if (flock(direntfd,LOCK_EX|LOCK_NB)) {
+ if (errno==EWOULDBLOCK) /* Valid directory in use. */
+ goto next_dirent_close_direntfd;
+ fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
+ }
+ chrooted_unlink_recursive(dirent_path);
+next_dirent_close_direntfd:
+ if (close(direntfd))
+ fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
+next_dirent_free_dirent_path:
+ g_free(dirent_path);
+ }
+ if (errno)
+ fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+ if (closedir(dir))
+ fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+}
+
+static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
{
- if (mkdir(dir,0711)) {
- if (errno!=EEXIST)
- fatal("Failed to create chroot directory \"%s\": %m",dir);
- unlink_recursive(dir);
- if (mkdir(dir,0711))
- fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+gint retries;
+
+ for (retries=0;retries<10;retries++) {
+struct stat statbuf;
+int dirfd;
+
+ if (mkdir(dir,0711)) {
+ if (errno!=EEXIST)
+ fatal("Failed to create chroot directory \"%s\": %m",dir);
+ chrooted_unlink_recursive(dir);
+ if (mkdir(dir,0711))
+ fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+ }
+ if (!lock)
+ break;
+ dirfd=open(dir,O_RDONLY);
+ if (dirfd==-1) {
+ if (errno!=ENOENT)
+ fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
+ continue;
+ }
+ /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
+ if (flock(dirfd,LOCK_EX))
+ fatal("Failed to lock created chroot directory \"%s\": %m",dir);
+ if (lstat(dir,&statbuf)) {
+ if (errno!=ENOENT)
+ fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
+ if (close(dirfd))
+ fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
+ continue;
+ }
+ /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
+ break;
}
if (chown(dir,uid,gid))
fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
}
+static void sandbox_server_rlimit(int resource,const gchar *resource_string,rlim_t rlim_max)
+{
+struct rlimit rlim;
+
+ rlim.rlim_cur=rlim.rlim_max=rlim_max;
+ if (setrlimit(resource,&rlim))
+ fatal("setrlimit(%s,%d): %m",resource_string,(int)rlim_max);
+ if (getrlimit(resource,&rlim))
+ fatal("getrlimit(%s,%d): %m",resource_string,(int)rlim_max);
+ if (rlim.rlim_cur!=rlim_max || rlim.rlim_max!=rlim_max)
+ fatal("Unsuccessful setrlimit(%s)",resource_string);
+}
+
+static void sandbox_server_mkdir_p(const gchar *dirpathname)
+{
+gchar *pathname=(/* de-const */ gchar *)captive_strdup_alloca(dirpathname);
+gchar *gs,*gs2;
+
+ /* Missing mkdir(2) of the last component path is intentional: */
+ for (gs=pathname;(gs2=strchr(gs,'/'));gs=gs2) {
+ *gs2='\0';
+ if (*pathname && mkdir(pathname,S_ISVTX|0777)) {
+ if (errno!=EEXIST)
+ fatal("Failed to mkdir(\"%s\"): %m",pathname);
+ }
+ *gs2++='/';
+ }
+}
+
static const gchar *chrooted_orbit_dir;
static void chroot_setup(gboolean fragile)
const gchar *want_uid_name=NULL;
gid_t want_gid=0;
char *endptr;
+const gchar *chroot_pid_hashkey_dir=NULL;
if (fragile) {
captive_sandbox_fd_closeup(2 /* STDERR */ +1);
want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
}
+ /* Prevent: GLib-WARNING **: getpwuid_r(): failed due to unknown user id (42)
+ * Try to invoke GLib g_get_any_init() before possible chroot(2) below.
+ */
+ g_get_user_name();
+ g_get_real_name();
+ g_get_home_dir();
+ g_get_tmp_dir();
+
+ /* Pre-resolve "link_get_tmpdir" symbol to prevent its later failed
+ * resolving in chroot(2) mode in Debian dynamic build.
+ */
+#ifdef HAVE_ORBIT_LINK
+ g_free(link_get_tmpdir()); /* returns g_strdup()ed string */
+#else
+ g_free(linc_get_tmpdir()); /* returns g_strdup()ed string */
+#endif
+
if (fragile && !optarg_chroot)
fatal("Fragile setuid/root environment but no --chroot set");
if (optarg_chroot) {
-const gchar *chroot_pid_dir,*chroot_pid_hashkey_dir;
+const gchar *chroot_pid_dir;
GRand *grand;
gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
gint gi;
}
g_rand_free(grand);
*s=0;
- chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
- chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
+ if (geteuid()==0) { /* Not 'fragile' as we can be native 'root'. */
+ chrooted_cleanuplockeddirs(optarg_chroot,"s-");
+ chrooted_cleanuplockeddirs(captive_printf_alloca("%s/tmp",optarg_chroot),"captive-orbit-");
+ }
+ chroot_pid_dir=captive_printf_alloca("%s/s-%d",optarg_chroot,(int)getpid());
+ chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+ TRUE); /* lock */
chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
- chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
+ chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+ FALSE); /* lock */
if (chroot(chroot_pid_hashkey_dir))
fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
if (chdir("/"))
umask(0000);
if (umask(0000)!=0000)
fatal("Failed to set umask(0%o): %m",0000);
- if (want_uid_name) {
- printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
- chrooted_orbit_dir=g_strdup_printf("/tmp/orbit-%s",want_uid_name);
- printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
- }
+ printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
}
if (fragile && !optarg_setgid)
fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
}
- /* Prepare /tmp for /tmp/orbit-$username directories for ORBit2
- * and also for parent's hardlink to its /tmp/captive-orbit-$pid directory. */
+ /* Prepare /t for /t/o-$PID directories for ORBit2
+ * and also for parent's hardlink to its /t/o-$pid directory. */
if (optarg_chroot) {
- if (mkdir("/tmp",S_ISVTX|0777))
- fatal("Failed to mkdir(\"%s\"): %m","/tmp");
+gchar *chrooted_orbit_dir_old;
+
+ if (mkdir("/t",S_ISVTX|0777)) {
+ if (errno!=EEXIST)
+ fatal("Failed to mkdir(\"%s\"): %m","/t");
+ }
if (mkdir("/etc",0700))
fatal("Failed to mkdir(\"%s\"): %m","/etc");
if (want_uid_name && want_uid && want_gid) {
FILE *f;
if (!(f=fopen("/etc/passwd","w")))
fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
- 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))
+ 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))
fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
if (fclose(f))
fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
}
+ g_assert(chroot_pid_hashkey_dir!=NULL);
+ chrooted_orbit_dir=g_strdup_printf("%s/t/o-%d",chroot_pid_hashkey_dir,getpid());
+ /* Last pathname component is not created: */
+ sandbox_server_mkdir_p(chrooted_orbit_dir);
+ /* Prepare '/tmp' for the initial CORBA_ORB_init() default path.
+ * Workaround sandbox_server_mkdir_p() does not create last component.
+ * Do not use '/tmp' directly as some distributions may set custom
+ * tmpdir pathname by $ENV{"TMPDIR"} etc.
+ */
+ sandbox_server_mkdir_p(captive_printf_alloca("%s/",g_get_tmp_dir()));
+ /* Set '0700' to prevent: Wrong permissions for ...
+ * by linc-1.0.1-1/src/linc-protocols.c/make_local_tmpdir()
+ */
+ if (mkdir(chrooted_orbit_dir,0700)) {
+ /* Do not: g_assert(errno==EEXIST);
+ * as if 'optarg_chroot' the whole chroot(2)ed directory should be ours.
+ */
+ fatal("Cannot created chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir);
+ }
+ /* Init 'orb' to pass through its linc_set_tmpdir() to not to be overriden below. */
+ {
+CORBA_ORB orb;
+CORBA_Environment ev;
+int orb_argc=1;
+gchar *orb_argv[]={
+ (gchar *)captive_strdup_alloca("captive-sandbox-server"),
+ NULL};
+
+ CORBA_exception_init(&ev);
+ /* libcaptive is single-threaded only, caller must lock it.
+ * If thread A spawned the sandbox while currently doing its own work
+ * and thread B calls the sandbox thread B waits on ORB_run()
+ * while the sandbox waits for the response of thread A ORB. Deadlock.
+ * "orbit-local-non-threaded-orb" requests thread unaware ORB.
+ */
+ orb=CORBA_ORB_init(&orb_argc,orb_argv,"orbit-local-non-threaded-orb",&ev);
+ if (orb==CORBA_OBJECT_NIL)
+ fatal("Cannot initialize CORBA ORB (CORBA_OBJECT_NIL): %m");
+ if (ev._major!=CORBA_NO_EXCEPTION)
+ fatal("Cannot initialize CORBA ORB (exception): %m");
+ }
+#ifdef HAVE_ORBIT_LINK
+ chrooted_orbit_dir_old=link_get_tmpdir(); /* returns g_strdup()ed string */
+#else
+ chrooted_orbit_dir_old=linc_get_tmpdir(); /* returns g_strdup()ed string */
+#endif
+ g_assert(chrooted_orbit_dir_old!=NULL);
+#ifdef HAVE_ORBIT_LINK
+ link_set_tmpdir(chrooted_orbit_dir);
+#else
+ linc_set_tmpdir(chrooted_orbit_dir);
+#endif
+ if (!*chrooted_orbit_dir_old)
+ fatal("Cannot detect chrooted_orbit_dir: --with-orbit-line incompatible with ORBit2 version");
+ if (rmdir(chrooted_orbit_dir_old))
+ fatal("Cannot remove old chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir_old);
+ g_free(chrooted_orbit_dir_old);
+ /* chmod(2) it to prevent mode limitation by
+ * active ulimit(2) of being executed by mount(8).
+ */
+ /* Set '0777' as our parent does not have 'captive' user permissions. */
+ if (chmod(chrooted_orbit_dir,S_ISVTX|0777))
+ fatal("Cannot chmod 0%o chrooted_orbit_dir \"%s\": %m",S_ISVTX|0777,chrooted_orbit_dir);
+ printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
+ }
+
+ if (fragile || !optarg_no_rlimit) {
+#define SANDBOX_SERVER_RLIMIT(what,how) sandbox_server_rlimit((what),G_STRINGIFY(what),(how))
+ SANDBOX_SERVER_RLIMIT(RLIMIT_NPROC,0);
+ SANDBOX_SERVER_RLIMIT(RLIMIT_MEMLOCK,0);
+ SANDBOX_SERVER_RLIMIT(RLIMIT_CORE,0);
+ SANDBOX_SERVER_RLIMIT(RLIMIT_FSIZE,0);
+ SANDBOX_SERVER_RLIMIT(RLIMIT_NOFILE,16); /* >=6; newer ORBit2/link require >6 */
+ /* FIXME: Why flock(dirfd,...) in chrooted_createdir() succeeds?: */
+ SANDBOX_SERVER_RLIMIT(RLIMIT_LOCKS,0);
+#undef SANDBOX_SERVER_RLIMIT
}
if (fragile) {
|G_LOG_LEVEL_DEBUG
));
+ /* Do not do it later than chroot_setup() as it requires it.
+ * On the other hand it is SETUID-fragile this way.
+ */
+ captive_standalone_init();
+
fatal_argv0=argv[0];
fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
chroot_setup(TRUE);
#endif /* MAINTAINER_MODE */
- /* Initialize the i18n stuff */
- setlocale(LC_ALL,"");
- bindtextdomain(PACKAGE,LOCALEDIR);
- textdomain(PACKAGE);
-
- /* Initialize GObject subsystem of GLib. */
- g_type_init();
-
captive_options_init(&options);
captive_options=&options; /* for parsing by 'CAPTIVE_POPT_INCLUDE' */