// check new private home directory (--private= option) - exit if it fails void fs_check_private_dir(void) { invalid_filename(cfg.home_private); // Expand the home directory char *tmp = expand_home(cfg.home_private, cfg.homedir); cfg.home_private = realpath(tmp, NULL); free(tmp); if (!cfg.home_private || !is_dir(cfg.home_private) || is_link(cfg.home_private) || strstr(cfg.home_private, "..")) { fprintf(stderr, "Error: invalid private directory\n"); exit(1); } // check home directory and chroot home directory have the same owner struct stat s2; int rv = stat(cfg.home_private, &s2); if (rv < 0) { fprintf(stderr, "Error: cannot find %s directory\n", cfg.home_private); exit(1); } struct stat s1; rv = stat(cfg.homedir, &s1); if (rv < 0) { fprintf(stderr, "Error: cannot find %s directory, full path name required\n", cfg.homedir); exit(1); } if (s1.st_uid != s2.st_uid) { printf("Error: --private directory should be owned by the current user\n"); exit(1); } }
// return 0 if file not found, 1 if found static int check_dir_or_file(const char *name) { assert(name); invalid_filename(name); struct stat s; char *fname; if (asprintf(&fname, "/etc/%s", name) == -1) errExit("asprintf"); if (arg_debug) printf("Checking %s\n", fname); if (stat(fname, &s) == -1) { if (arg_debug) printf("Warning: file %s not found.\n", fname); return 0; } // dir or regular file if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) { free(fname); return 1; } if (!is_link(fname)) { free(fname); return 1; } fprintf(stderr, "Error: invalid file type, %s.\n", fname); exit(1); }
//*********************************************************************************** // --private-home //*********************************************************************************** static char *check_dir_or_file(const char *name) { assert(name); // basic checks invalid_filename(name); if (arg_debug) printf("Private home: checking %s\n", name); // expand home directory char *fname = expand_home(name, cfg.homedir); assert(fname); // If it doesn't start with '/', it must be relative to homedir if (fname[0] != '/') { char* tmp; if (asprintf(&tmp, "%s/%s", cfg.homedir, fname) == -1) errExit("asprintf"); free(fname); fname = tmp; } // we allow only files in user home directory or symbolic links to files or directories owned by the user struct stat s; if (lstat(fname, &s) == 0 && S_ISLNK(s.st_mode)) { if (stat(fname, &s) == 0) { if (s.st_uid != getuid()) { fprintf(stderr, "Error: symbolic link %s to file or directory not owned by the user\n", fname); exit(1); } return fname; } else { fprintf(stderr, "Error: invalid file %s\n", name); exit(1); } } else { // check the file is in user home directory, a full home directory is not allowed char *rname = realpath(fname, NULL); if (!rname || strncmp(rname, cfg.homedir, strlen(cfg.homedir)) != 0 || strcmp(rname, cfg.homedir) == 0) { fprintf(stderr, "Error: invalid file %s\n", name); exit(1); } // only top files and directories in user home are allowed char *ptr = rname + strlen(cfg.homedir); assert(*ptr != '\0'); ptr = strchr(++ptr, '/'); if (ptr) { if (*ptr != '\0') { fprintf(stderr, "Error: only top files and directories in user home are allowed\n"); exit(1); } } free(fname); return rname; } }
// return 1 if found, 0 if not found static char *check_dir_or_file(const char *name) { assert(name); invalid_filename(name); struct stat s; char *fname = NULL; int i = 0; while (paths[i]) { if (asprintf(&fname, "%s/%s", paths[i], name) == -1) errExit("asprintf"); if (arg_debug) printf("Checking %s/%s\n", paths[i], name); if (stat(fname, &s) == 0 && !S_ISDIR(s.st_mode)) // do not allow directories break; // file found free(fname); fname = NULL; i++; } if (!fname) { // fprintf(stderr, "Warning: file %s not found\n", name); return NULL; } free(fname); return paths[i]; }
int ml_utime(struct afp_volume * vol, const char * path, struct utimbuf * timebuf) { int ret=0; unsigned int dirid; struct afp_file_info fp; char basename[AFP_MAX_PATH]; char converted_path[AFP_MAX_PATH]; int rc; if (volume_is_readonly(vol)) return -EACCES; memset(&fp,0,sizeof(struct afp_file_info)); fp.modification_date=timebuf->modtime; if (invalid_filename(vol->server,path)) return -ENAMETOOLONG; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) { return -EINVAL; } ret=appledouble_utime(vol,path,timebuf); if (ret<0) return ret; if (ret==1) return 0; get_dirid(vol,converted_path,basename,&dirid ); if (is_dir(vol,dirid,basename)) { rc=afp_setdirparms(vol, dirid,basename, kFPModDateBit, &fp); } else { rc=afp_setfileparms(vol, dirid,basename, kFPModDateBit, &fp); } switch(rc) { case kFPNoErr: break; case kFPAccessDenied: return -EACCES; case kFPObjectNotFound: return -ENOENT; case kFPBitmapErr: case kFPMiscErr: case kFPObjectTypeErr: case kFPParamErr: default: break; } return -ret; }
void check_netfilter_file(const char *fname) { EUID_ASSERT(); invalid_filename(fname); if (is_dir(fname) || is_link(fname) || strstr(fname, "..") || access(fname, R_OK )) { fprintf(stderr, "Error: invalid network filter file %s\n", fname); exit(1); } }
int ml_rmdir(struct afp_volume * vol, const char *path) { int ret,rc; unsigned int dirid; char basename[AFP_MAX_PATH]; char converted_path[AFP_MAX_PATH]; if (invalid_filename(vol->server,path)) return -ENAMETOOLONG; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; if (volume_is_readonly(vol)) return -EACCES; ret=appledouble_rmdir(vol,path); if (ret<0) return ret; if (ret==1) return 0; get_dirid(vol, converted_path, basename, &dirid); if (!is_dir(vol,dirid,basename)) return -ENOTDIR; rc=afp_delete(vol,dirid,basename); switch(rc) { case kFPAccessDenied: ret=EACCES; break; case kFPObjectLocked: ret=EBUSY; break; case kFPObjectNotFound: ret=ENOENT; break; case kFPVolLocked: ret=EACCES; break; case kFPDirNotEmpty: ret=ENOTEMPTY; break; case kFPObjectTypeErr: case kFPMiscErr: case kFPParamErr: case -1: ret=EINVAL; break; default: remove_did_entry(vol,converted_path); ret=0; } return -ret; }
int ml_mkdir(struct afp_volume * vol, const char * path, mode_t mode) { int ret,rc; unsigned int result_did; char basename[AFP_MAX_PATH]; char converted_path[AFP_MAX_PATH]; unsigned int dirid; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; if (invalid_filename(vol->server,path)) return -ENAMETOOLONG; if (volume_is_readonly(vol)) return -EACCES; ret=appledouble_mkdir(vol,path,mode); if (ret<0) return ret; if (ret==1) return 0; get_dirid(vol,converted_path,basename,&dirid); rc = afp_createdir(vol,dirid, basename,&result_did); switch (rc) { case kFPAccessDenied: ret = EACCES; break; case kFPDiskFull: ret = ENOSPC; break; case kFPObjectNotFound: ret = ENOENT; break; case kFPObjectExists: ret = EEXIST; break; case kFPVolLocked: ret = EBUSY; break; case kFPFlatVol: case kFPMiscErr: case kFPParamErr: case -1: ret = EFAULT; break; default: ret =0; } return -ret; }
int ml_open(struct afp_volume * volume, const char *path, int flags, struct afp_file_info **newfp) { /* FIXME: doesn't handle create properly */ struct afp_file_info * fp ; int ret; unsigned int dirid; char converted_path[AFP_MAX_PATH]; if (convert_path_to_afp(volume->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; if (invalid_filename(volume->server,converted_path)) return -ENAMETOOLONG; if (volume_is_readonly(volume) && (flags & (O_WRONLY|O_RDWR|O_TRUNC|O_APPEND|O_CREAT))) return -EACCES; if ((fp=malloc(sizeof(*fp)))==NULL) { return -1; } *newfp=fp; memset(fp,0,sizeof(*fp)); ret=appledouble_open(volume,path,flags,fp); if (ret<0) return ret; if (ret==1) goto out; if (get_dirid(volume,converted_path,fp->basename,&dirid)<0) return -ENOENT; fp->did=dirid; ret=ll_open(volume,converted_path,flags,fp); if (ret<0) goto error; out: return 0; error: free(fp); return ret; }
// return 1 if found, 0 if not found static char *check_dir_or_file(const char *name) { assert(name); invalid_filename(name); struct stat s; char *fname = NULL; int i = 0; while (paths[i]) { if (asprintf(&fname, "%s/%s", paths[i], name) == -1) errExit("asprintf"); if (arg_debug) printf("Checking %s/%s\n", paths[i], name); if (stat(fname, &s) == 0 && !S_ISDIR(s.st_mode)) { // do not allow directories // check symlink to firejail executable in /usr/local/bin if (strcmp(paths[i], "/usr/local/bin") == 0 && is_link(fname)) { char *actual_path = realpath(fname, NULL); if (actual_path) { char *ptr = strstr(actual_path, "/firejail"); if (ptr && strlen(ptr) == strlen("/firejail")) { if (arg_debug) printf("firejail exec symlink detected\n"); free(actual_path); free(fname); fname = NULL; i++; continue; } free(actual_path); } } break; // file found } free(fname); fname = NULL; i++; } if (!fname) { if (arg_debug) fprintf(stderr, "Warning: file %s not found\n", name); return NULL; } free(fname); return paths[i]; }
static void check_dir_or_file(const char *name) { assert(name); struct stat s; invalid_filename(name); char *fname = expand_home(name, cfg.homedir); if (!fname) { fprintf(stderr, "Error: file %s not found.\n", name); exit(1); } if (fname[0] != '/') { // If it doesn't start with '/', it must be relative to homedir char* tmp; if (asprintf(&tmp, "%s/%s", cfg.homedir, fname) == -1) errExit("asprintf"); free(fname); fname = tmp; } if (arg_debug) printf("Checking %s\n", fname); if (stat(fname, &s) == -1) { fprintf(stderr, "Error: file %s not found.\n", fname); exit(1); } // check uid uid_t uid = getuid(); gid_t gid = getgid(); if (s.st_uid != uid || s.st_gid != gid) { fprintf(stderr, "Error: only files or directories created by the current user are allowed.\n"); exit(1); } // dir or regular file if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) { free(fname); return; } if (!is_link(fname)) { free(fname); return; } fprintf(stderr, "Error: invalid file type, %s.\n", fname); exit(1); }
void set_cgroup(const char *path) { EUID_ASSERT(); invalid_filename(path); // path starts with /sys/fs/cgroup if (strncmp(path, "/sys/fs/cgroup", 14) != 0) goto errout; // path ends in tasks char *ptr = strstr(path, "tasks"); if (!ptr) goto errout; if (*(ptr + 5) != '\0') goto errout; // no .. traversal ptr = strstr(path, ".."); if (ptr) goto errout; // tasks file exists struct stat s; if (stat(path, &s) == -1) goto errout; // task file belongs to the user running the sandbox if (s.st_uid != getuid() && s.st_gid != getgid()) goto errout2; // add the task to cgroup /* coverity[toctou] */ FILE *fp = fopen(path, "a"); if (!fp) goto errout; pid_t pid = getpid(); int rv = fprintf(fp, "%d\n", pid); (void) rv; fclose(fp); return; errout: fprintf(stderr, "Error: invalid cgroup\n"); exit(1); errout2: fprintf(stderr, "Error: you don't have permissions to use this control group\n"); exit(1); }
int ml_truncate(struct afp_volume * vol, const char * path, off_t offset) { int ret=0; char converted_path[AFP_MAX_PATH]; struct afp_file_info *fp; int flags; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; /* The approach here is to get the forkid by calling ml_open() (and not afp_openfork). Note the fake afp_file_info used just to grab this forkid. */ if (invalid_filename(vol->server,converted_path)) return -ENAMETOOLONG; if (volume_is_readonly(vol)) return -EACCES; ret=appledouble_truncate(vol,path,offset); if (ret<0) return ret; if (ret==1) return 0; /* Here, we're going to use the untranslated path since it is translated through the ml_open() */ flags=O_WRONLY; if ((ml_open(vol,path,flags,&fp))) { return ret; }; if ((ret=ll_zero_file(vol,fp->forkid,0))) goto out; afp_closefork(vol,fp->forkid); remove_opened_fork(vol, fp); free(fp); out: return -ret; }
void fs_mkfile(const char *name) { EUID_ASSERT(); // check file name invalid_filename(name); char *expanded = expand_home(name, cfg.homedir); if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0) { fprintf(stderr, "Error: only files in user home are supported by mkfile\n"); exit(1); } struct stat s; if (stat(expanded, &s) == 0) { // file exists, do nothing goto doexit; } // create file pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // drop privileges drop_privs(0); FILE *fp = fopen(expanded, "w"); if (!fp) fprintf(stderr, "Warning: cannot create %s file\n", expanded); else { fclose(fp); int rv = chmod(expanded, 0600); (void) rv; } exit(0); } // wait for the child to finish waitpid(child, NULL, 0); doexit: free(expanded); }
int ml_close(struct afp_volume * volume, const char * path, struct afp_file_info * fp) { int ret=0; char converted_path[AFP_MAX_PATH]; if (convert_path_to_afp(volume->server->path_encoding, converted_path, (char *) path,AFP_MAX_PATH)) { return -EINVAL; } if (invalid_filename(volume->server,converted_path)) return -ENAMETOOLONG; /* The logic here is that if we don't have an fp anymore, then the fork must already be closed. */ if (!fp) return EBADF; if (fp->icon) { free(fp->icon); } if (fp->resource) { return appledouble_close(volume,fp); } switch(afp_closefork(volume,fp->forkid)) { case kFPNoErr: break; default: case kFPParamErr: case kFPMiscErr: ret=EIO; goto error; } remove_opened_fork(volume, fp); error: return ret; }
void fs_mkdir(const char *name) { EUID_ASSERT(); // check directory name invalid_filename(name, 0); // no globbing char *expanded = expand_macros(name); if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0 && strncmp(expanded, "/tmp", 4) != 0) { fprintf(stderr, "Error: only directories in user home or /tmp are supported by mkdir\n"); exit(1); } struct stat s; if (stat(expanded, &s) == 0) { // file exists, do nothing goto doexit; } // create directory pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // drop privileges drop_privs(0); // create directory mkdir_recursive(expanded); #ifdef HAVE_GCOV __gcov_flush(); #endif _exit(0); } // wait for the child to finish waitpid(child, NULL, 0); doexit: free(expanded); }
void check_netns(const char *nsname) { if (strchr(nsname, '/') || strstr(nsname, "..")) { fprintf(stderr, "Error: invalid netns name %s\n", nsname); exit(1); } invalid_filename(nsname, 0); // no globbing char *control_file = netns_control_file(nsname); EUID_ASSERT(); struct stat st; if (lstat(control_file, &st)) { fprintf(stderr, "Error: invalid netns '%s' (%s: %s)\n", nsname, control_file, strerror(errno)); exit(1); } if (!S_ISREG(st.st_mode)) { fprintf(stderr, "Error: invalid netns '%s' (%s: not a regular file)\n", nsname, control_file); exit(1); } free(control_file); }
void fs_mkdir(const char *name) { EUID_ASSERT(); // check directory name invalid_filename(name); char *expanded = expand_home(name, cfg.homedir); if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0) { fprintf(stderr, "Error: only directories in user home are supported by mkdir\n"); exit(1); } struct stat s; if (stat(expanded, &s) == 0) { // file exists, do nothing goto doexit; } // create directory pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // drop privileges drop_privs(0); // create directory if (mkdir(expanded, 0700) == -1) fprintf(stderr, "Warning: cannot create %s directory\n", expanded); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); doexit: free(expanded); }
void fs_mkfile(const char *name) { EUID_ASSERT(); // check file name invalid_filename(name, 0); // no globbing char *expanded = expand_macros(name); if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0 && strncmp(expanded, "/tmp", 4) != 0) { fprintf(stderr, "Error: only files in user home or /tmp are supported by mkfile\n"); exit(1); } struct stat s; if (stat(expanded, &s) == 0) { // file exists, do nothing goto doexit; } // create file touch_file_as_user(expanded, 0600); doexit: free(expanded); }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno, const char *fname) { EUID_ASSERT(); // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it else cfg.profile_ignore[j] = str; return 0; } // mkdir if (strncmp(ptr, "mkdir ", 6) == 0) { fs_mkdir(ptr + 6); return 1; // process mkdir again while applying blacklists } // mkfile if (strncmp(ptr, "mkfile ", 7) == 0) { fs_mkfile(ptr + 7); return 1; // process mkfile again while applying blacklists } // sandbox name else if (strncmp(ptr, "name ", 5) == 0) { cfg.name = ptr + 5; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } else if (strcmp(ptr, "ipc-namespace") == 0) { arg_ipc = 1; return 0; } // seccomp, caps, private, user namespace else if (strcmp(ptr, "noroot") == 0) { #if HAVE_USERNS if (checkcfg(CFG_USERNS)) check_user_namespace(); else warning_feature_disabled("noroot"); #endif return 0; } else if (strcmp(ptr, "nonewprivs") == 0) { arg_nonewprivs = 1; return 0; } else if (strcmp(ptr, "seccomp") == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) arg_seccomp = 1; else warning_feature_disabled("seccomp"); #endif return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "tracelog") == 0) { arg_tracelog = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } if (strncmp(ptr, "private-home ", 13) == 0) { #ifdef HAVE_PRIVATE_HOME if (checkcfg(CFG_PRIVATE_HOME)) { if (cfg.home_private_keep) { if ( asprintf(&cfg.home_private_keep, "%s,%s", cfg.home_private_keep, ptr + 13) < 0 ) errExit("asprintf"); } else cfg.home_private_keep = ptr + 13; arg_private = 1; } else warning_feature_disabled("private-home"); #endif return 0; } else if (strcmp(ptr, "allusers") == 0) { arg_allusers = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "private-tmp") == 0) { arg_private_tmp = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "nosound") == 0) { arg_nosound = 1; return 0; } else if (strcmp(ptr, "novideo") == 0) { arg_novideo = 1; return 0; } else if (strcmp(ptr, "no3d") == 0) { arg_no3d = 1; return 0; } else if (strcmp(ptr, "allow-private-blacklist") == 0) { arg_allow_private_blacklist = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) arg_netfilter = 1; else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "netfilter6 ", 11) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter6 = 1; arg_netfilter6_file = strdup(ptr + 11); if (!arg_netfilter6_file) errExit("strdup"); check_netfilter_file(arg_netfilter6_file); } else warning_feature_disabled("networking"); #endif return 0; } else if (strcmp(ptr, "net none") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; cfg.interface0.configured = 0; cfg.interface1.configured = 0; cfg.interface2.configured = 0; cfg.interface3.configured = 0; } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "net ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { #ifdef HAVE_NETWORK_RESTRICTED // compile time restricted networking if (getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } #endif // run time restricted networking if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } if (strcmp(ptr + 4, "lo") == 0) { fprintf(stderr, "Error: cannot attach to lo device\n"); exit(1); } Bridge *br; if (cfg.bridge0.configured == 0) br = &cfg.bridge0; else if (cfg.bridge1.configured == 0) br = &cfg.bridge1; else if (cfg.bridge2.configured == 0) br = &cfg.bridge2; else if (cfg.bridge3.configured == 0) br = &cfg.bridge3; else { fprintf(stderr, "Error: maximum 4 network devices are allowed\n"); exit(1); } net_configure_bridge(br, ptr + 4); } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "veth-name ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } br->veth_name = strdup(ptr + 10); if (br->veth_name == NULL) errExit("strdup"); if (*br->veth_name == '\0') { fprintf(stderr, "Error: no veth-name configured\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "iprange ", 8) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->iprange_start || br->iprange_end) { fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n"); exit(1); } // parse option arguments char *firstip = ptr + 8; char *secondip = firstip; while (*secondip != '\0') { if (*secondip == ',') break; secondip++; } if (*secondip == '\0') { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } *secondip = '\0'; secondip++; // check addresses if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) || br->iprange_start >= br->iprange_end) { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) { fprintf(stderr, "Error: IP range addresses not in network range\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "mac ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (mac_not_zero(br->macsandbox)) { fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n"); exit(1); } // read the address if (atomac(ptr + 4, br->macsandbox)) { fprintf(stderr, "Error: invalid MAC address\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "mtu ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) { fprintf(stderr, "Error: invalid mtu value\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "ip ", 3) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ipsandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "ip6 ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined // todo: verify ipv6 syntax br->ip6sandbox = ptr + 4; // if (atoip(argv[i] + 5, &br->ipsandbox)) { // fprintf(stderr, "Error: invalid IP address\n"); // exit(1); // } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "defaultgw ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { if (atoip(ptr + 10, &cfg.defaultgw)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } if (strcmp(ptr, "apparmor") == 0) { #ifdef HAVE_APPARMOR arg_apparmor = 1; #endif return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { if (cfg.protocol) { fwarning("a protocol list is present, the new list \"%s\" will not be installed\n", ptr + 9); return 0; } // store list cfg.protocol = strdup(ptr + 9); if (!cfg.protocol) errExit("strdup"); } else warning_feature_disabled("seccomp"); #endif return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4, SETENV); return 0; } if (strncmp(ptr, "rmenv ", 6) == 0) { env_store(ptr + 6, RMENV); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list = seccomp_check_list(ptr + 8); } else if (!arg_quiet) warning_feature_disabled("seccomp"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_drop = seccomp_check_list(ptr + 13); } else warning_feature_disabled("seccomp"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_keep= seccomp_check_list(ptr + 13); } else warning_feature_disabled("seccomp"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify caps list and exit if problems caps_check_list(arg_caps_list, NULL); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify caps list and exit if problems caps_check_list(arg_caps_list, NULL); return 0; } // hostname if (strncmp(ptr, "hostname ", 9) == 0) { cfg.hostname = ptr + 9; return 0; } // hosts-file if (strncmp(ptr, "hosts-file ", 11) == 0) { cfg.hosts_file = fs_check_hosts_file(ptr + 11); return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // nice value if (strncmp(ptr, "nice ", 4) == 0) { cfg.nice = atoi(ptr + 5); if (getuid() != 0 &&cfg.nice < 0) cfg.nice = 0; arg_nice = 1; return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // writable-etc if (strcmp(ptr, "writable-etc") == 0) { if (cfg.etc_private_keep) { fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n"); exit(1); } arg_writable_etc = 1; return 0; } if (strcmp(ptr, "machine-id") == 0) { arg_machineid = 1; return 0; } // writable-var if (strcmp(ptr, "writable-var") == 0) { arg_writable_var = 1; return 0; } if (strcmp(ptr, "writable-var-log") == 0) { arg_writable_var_log = 1; return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } if (strcmp(ptr, "x11 none") == 0) { arg_x11_block = 1; return 0; } if (strcmp(ptr, "x11 xephyr") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xephyr(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xorg") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) arg_x11_xorg = 1; else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xpra") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xpra(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xvfb") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xvfb(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { if (arg_writable_etc) { fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); exit(1); } if (cfg.etc_private_keep) { if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.etc_private_keep = ptr + 12; } arg_private_etc = 1; return 0; } // private /opt list of files and directories if (strncmp(ptr, "private-opt ", 12) == 0) { if (cfg.opt_private_keep) { if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.opt_private_keep = ptr + 12; } arg_private_opt = 1; return 0; } // private /srv list of files and directories if (strncmp(ptr, "private-srv ", 12) == 0) { if (cfg.srv_private_keep) { if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.srv_private_keep = ptr + 12; } arg_private_srv = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { if (cfg.bin_private_keep) { if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.bin_private_keep = ptr + 12; } arg_private_bin = 1; return 0; } #ifdef HAVE_OVERLAYFS if (strncmp(ptr, "overlay-named ", 14) == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; arg_overlay_keep = 1; arg_overlay_reuse = 1; char *subdirname = ptr + 14; if (subdirname == '\0') { fprintf(stderr, "Error: invalid overlay option\n"); exit(1); } // check name invalid_filename(subdirname); if (strstr(subdirname, "..") || strstr(subdirname, "/")) { fprintf(stderr, "Error: invalid overlay name\n"); exit(1); } cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse); } return 0; } else if (strcmp(ptr, "overlay-tmpfs") == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; return 0; } } else if (strcmp(ptr, "overlay") == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; arg_overlay_keep = 1; char *subdirname; if (asprintf(&subdirname, "%d", getpid()) == -1) errExit("asprintf"); cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse); free(subdirname); return 0; } } #endif // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { #ifdef HAVE_BIND if (checkcfg(CFG_BIND)) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: missing second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } if (is_link(dname1) || is_link(dname2)) { fprintf(stderr, "Symbolic links are not allowed for bind command\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } else warning_feature_disabled("bind"); #endif return 0; } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { check_unsigned(ptr + 14, "Error: invalid rlimit in profile file: "); sscanf(ptr + 14, "%llu", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: "); sscanf(ptr + 13, "%llu", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: "); sscanf(ptr + 13, "%llu", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { check_unsigned(ptr + 18, "Error: invalid rlimit in profile file: "); sscanf(ptr + 18, "%llu", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } if (strncmp(ptr, "join-or-start ", 14) == 0) { // try to join by name only pid_t pid; if (!name2pid(ptr + 14, &pid)) { if (!cfg.shell && !arg_shell_none) cfg.shell = guess_shell(); // find first non-option arg int i; for (i = 1; i < cfg.original_argc && strncmp(cfg.original_argv[i], "--", 2) != 0; i++); join(pid, cfg.original_argc,cfg.original_argv, i + 1); exit(0); } // set sandbox name and start normally cfg.name = ptr + 14; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "blacklist-nolog ", 16) == 0) ptr += 16; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { #ifdef HAVE_WHITELIST if (checkcfg(CFG_WHITELIST)) { arg_whitelist = 1; ptr += 10; } else return 0; #else return 0; #endif } else if (strncmp(ptr, "nowhitelist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "read-write ", 11) == 0) ptr += 11; else if (strncmp(ptr, "noexec ", 7) == 0) ptr += 7; else if (strncmp(ptr, "tmpfs ", 6) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n"); exit(1); } ptr += 6; } else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
void profile_read(const char *fname) { EUID_ASSERT(); // exit program if maximum include level was reached if (include_level > MAX_INCLUDE_LEVEL) { fprintf(stderr, "Error: maximum profile include level was reached\n"); exit(1); } // check file invalid_filename(fname); if (strlen(fname) == 0 || is_dir(fname)) { fprintf(stderr, "Error: invalid profile file\n"); exit(1); } if (access(fname, R_OK)) { // if the file ends in ".local", do not exit const char *base = gnu_basename(fname); char *ptr = strstr(base, ".local"); if (ptr && strlen(ptr) == 6) return; fprintf(stderr, "Error: cannot access profile file\n"); exit(1); } // allow debuggers if (arg_allow_debuggers) { char *tmp = strrchr(fname, '/'); if (tmp && *(tmp + 1) != '\0') { tmp++; if (strcmp(tmp, "disable-devel.inc") == 0) return; } } // open profile file: FILE *fp = fopen(fname, "r"); if (fp == NULL) { fprintf(stderr, "Error: cannot open profile file %s\n", fname); exit(1); } int msg_printed = 0; // read the file line by line char buf[MAX_READ + 1]; int lineno = 0; while (fgets(buf, MAX_READ, fp)) { ++lineno; // remove empty space - ptr in allocated memory char *ptr = line_remove_spaces(buf); if (ptr == NULL) continue; // comments if (*ptr == '#' || *ptr == '\0') { free(ptr); continue; } // process quiet if (strcmp(ptr, "quiet") == 0) { arg_quiet = 1; free(ptr); continue; } if (!msg_printed) { if (!arg_quiet) fprintf(stderr, "Reading profile %s\n", fname); msg_printed = 1; } // process include if (strncmp(ptr, "include ", 8) == 0) { include_level++; // extract profile filename and new skip params char *newprofile = ptr + 8; // profile name // expand ${HOME}/ in front of the new profile file char *newprofile2 = expand_home(newprofile, cfg.homedir); // recursivity profile_read((newprofile2)? newprofile2:newprofile); include_level--; if (newprofile2) free(newprofile2); free(ptr); continue; } // verify syntax, exit in case of error if (profile_check_line(ptr, lineno, fname)) profile_add(ptr); // we cannot free ptr here, data is extracted from ptr and linked as a pointer in cfg structure // else { // free(ptr); // } #ifdef HAVE_GCOV __gcov_flush(); #endif } fclose(fp); }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno, const char *fname) { EUID_ASSERT(); // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it else cfg.profile_ignore[j] = str; return 0; } // mkdir if (strncmp(ptr, "mkdir ", 6) == 0) { fs_mkdir(ptr + 6); return 0; } // sandbox name else if (strncmp(ptr, "name ", 5) == 0) { cfg.name = ptr + 5; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } else if (strcmp(ptr, "ipc-namespace") == 0) { arg_ipc = 1; return 0; } // seccomp, caps, private, user namespace else if (strcmp(ptr, "noroot") == 0) { #if HAVE_USERNS if (checkcfg(CFG_USERNS)) check_user_namespace(); else fprintf(stderr, "Warning: user namespace feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "nonewprivs") == 0) { arg_nonewprivs = 1; return 0; } else if (strcmp(ptr, "seccomp") == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) arg_seccomp = 1; else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "tracelog") == 0) { arg_tracelog = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "private-tmp") == 0) { arg_private_tmp = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "nosound") == 0) { arg_nosound = 1; arg_private_dev = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) arg_netfilter = 1; else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter6 ", 11) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter6 = 1; arg_netfilter6_file = strdup(ptr + 11); if (!arg_netfilter6_file) errExit("strdup"); check_netfilter_file(arg_netfilter6_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "net none") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; cfg.interface0.configured = 0; cfg.interface1.configured = 0; cfg.interface2.configured = 0; cfg.interface3.configured = 0; } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "net ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { #ifdef HAVE_NETWORK_RESTRICTED // compile time restricted networking if (getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } #endif // run time restricted networking if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } if (strcmp(ptr + 4, "lo") == 0) { fprintf(stderr, "Error: cannot attach to lo device\n"); exit(1); } Bridge *br; if (cfg.bridge0.configured == 0) br = &cfg.bridge0; else if (cfg.bridge1.configured == 0) br = &cfg.bridge1; else if (cfg.bridge2.configured == 0) br = &cfg.bridge2; else if (cfg.bridge3.configured == 0) br = &cfg.bridge3; else { fprintf(stderr, "Error: maximum 4 network devices are allowed\n"); exit(1); } net_configure_bridge(br, ptr + 4); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "iprange ", 8) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->iprange_start || br->iprange_end) { fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n"); exit(1); } // parse option arguments char *firstip = ptr + 8; char *secondip = firstip; while (*secondip != '\0') { if (*secondip == ',') break; secondip++; } if (*secondip == '\0') { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } *secondip = '\0'; secondip++; // check addresses if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) || br->iprange_start >= br->iprange_end) { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) { fprintf(stderr, "Error: IP range addresses not in network range\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } // from here else if (strncmp(ptr, "mac ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (mac_not_zero(br->macsandbox)) { fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n"); exit(1); } // read the address if (atomac(ptr + 4, br->macsandbox)) { fprintf(stderr, "Error: invalid MAC address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "mtu ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) { fprintf(stderr, "Error: invalid mtu value\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip ", 3) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ipsandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip6 ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined // todo: verify ipv6 syntax br->ip6sandbox = ptr + 4; // if (atoip(argv[i] + 5, &br->ipsandbox)) { // fprintf(stderr, "Error: invalid IP address\n"); // exit(1); // } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "defaultgw ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { if (atoip(ptr + 10, &cfg.defaultgw)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) protocol_store(ptr + 9); else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list = strdup(ptr + 8); if (!cfg.seccomp_list) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_drop = strdup(ptr + 13); if (!cfg.seccomp_list_drop) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_keep= strdup(ptr + 13); if (!cfg.seccomp_list_keep) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // hostname if (strncmp(ptr, "hostname ", 9) == 0) { cfg.hostname = ptr + 9; return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // nice value if (strncmp(ptr, "nice ", 4) == 0) { cfg.nice = atoi(ptr + 5); if (getuid() != 0 &&cfg.nice < 0) cfg.nice = 0; arg_nice = 1; return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // writable-etc if (strcmp(ptr, "writable-etc") == 0) { if (cfg.etc_private_keep) { fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n"); exit(1); } arg_writable_etc = 1; return 0; } // writable-var if (strcmp(ptr, "writable-var") == 0) { arg_writable_var = 1; return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { if (arg_writable_etc) { fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); exit(1); } cfg.etc_private_keep = ptr + 12; fs_check_etc_list(); arg_private_etc = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { cfg.bin_private_keep = ptr + 12; arg_private_bin = 1; fs_check_bin_list(); return 0; } // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { #ifdef HAVE_BIND if (checkcfg(CFG_BIND)) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: missing second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } if (is_link(dname1) || is_link(dname2)) { fprintf(stderr, "Symbolic links are not allowed for bind command\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } else { fprintf(stderr, "Warning: bind feature is disabled in Firejail configuration file\n"); return 0; } #else return 0; #endif } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { ptr += 14; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { ptr += 18; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } // read-write if (strncmp(ptr, "read-write ", 11) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: read-write command is available only for root user\n"); exit(1); } fs_rdwr_add(ptr + 11); return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "blacklist-nolog ", 16) == 0) ptr += 16; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { #ifdef HAVE_WHITELIST if (checkcfg(CFG_WHITELIST)) { arg_whitelist = 1; ptr += 10; } else return 0; #else return 0; #endif } else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "tmpfs ", 6) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n"); exit(1); } ptr += 6; } else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno) { // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it cfg.profile_ignore[j] = str; return 0; } // seccomp, caps, private, user namespace if (strcmp(ptr, "noroot") == 0) { check_user_namespace(); return 0; } else if (strcmp(ptr, "seccomp") == 0) { arg_seccomp = 1; return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { arg_netfilter = 1; return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); return 0; } else if (strcmp(ptr, "net none") == 0) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { protocol_store(ptr + 9); return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { arg_seccomp = 1; #ifdef HAVE_SECCOMP cfg.seccomp_list = strdup(ptr + 8); if (!cfg.seccomp_list) errExit("strdup"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { arg_seccomp = 1; #ifdef HAVE_SECCOMP cfg.seccomp_list_drop = strdup(ptr + 13); if (!cfg.seccomp_list_drop) errExit("strdup"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { arg_seccomp = 1; #ifdef HAVE_SECCOMP cfg.seccomp_list_keep= strdup(ptr + 13); if (!cfg.seccomp_list_keep) errExit("strdup"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } // private home list of files and directories if ((strncmp(ptr, "private.keep ", 13) == 0) || (strncmp(ptr, "private-home ", 13) == 0)) { cfg.home_private_keep = ptr + 13; fs_check_home_list(); arg_private = 1; return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { cfg.etc_private_keep = ptr + 12; fs_check_etc_list(); arg_private_etc = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { cfg.bin_private_keep = ptr + 12; fs_check_bin_list(); arg_private_bin = 1; return 0; } // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: mising second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { ptr += 14; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { ptr += 18; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { arg_whitelist = 1; ptr += 10; } else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "tmpfs ", 6) == 0) ptr += 6; else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
int ml_creat(struct afp_volume * volume, const char *path, mode_t mode) { int ret=0; char basename[AFP_MAX_PATH]; unsigned int dirid; struct afp_file_info fp; int rc; char converted_path[AFP_MAX_PATH]; if (convert_path_to_afp(volume->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; if (volume_is_readonly(volume)) return -EACCES; ret=appledouble_creat(volume,path,mode); if (ret<0) return ret; if (ret==1) return 0; if (invalid_filename(volume->server,converted_path)) return -ENAMETOOLONG; get_dirid(volume, converted_path, basename, &dirid); rc=afp_createfile(volume,kFPSoftCreate, dirid,basename); switch(rc) { case kFPAccessDenied: ret=EACCES; break; case kFPDiskFull: ret=ENOSPC; break; case kFPObjectExists: ret=EEXIST; break; case kFPObjectNotFound: ret=ENOENT; break; case kFPFileBusy: case kFPVolLocked: ret=EBUSY; break; case kFPNoErr: ret=0; break; default: case kFPParamErr: case kFPMiscErr: ret=EIO; } if (ret) return -ret; /* If we don't support unixprivs, just exit */ if (~ volume->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) return 0; /* Figure out the privs of the file we just created */ if ((ret=get_unixprivs(volume, dirid,basename, &fp))) return rc; if (ret) return -ret; if (fp.unixprivs.permissions==mode) return 0; fp.unixprivs.ua_permissions=0; fp.unixprivs.permissions=mode; fp.isdir=0; /* Anything you make with mknod is a file */ /* note that we're not monkeying with the ownership here */ rc=set_unixprivs(volume, dirid, basename, &fp); switch(rc) { case kFPAccessDenied: ret=EPERM; goto error; case kFPObjectNotFound: ret=ENOENT; goto error; case 0: ret=0; break; case kFPBitmapErr: case kFPMiscErr: case kFPObjectTypeErr: case kFPParamErr: default: ret=EIO; goto error; } error: return -ret; }
int ml_chown(struct afp_volume * vol, const char * path, uid_t uid, gid_t gid) { int ret; struct afp_file_info fp; int rc; unsigned int dirid; char basename[AFP_MAX_PATH]; char converted_path[AFP_MAX_PATH]; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) return -EINVAL; if (invalid_filename(vol->server,converted_path)) return -ENAMETOOLONG; if (volume_is_readonly(vol)) return -EACCES; ret=appledouble_chown(vol,path,uid,gid); if (ret<0) return ret; if (ret==1) return 0; /* There's no way to do this in AFP < 3.0 */ if (~ vol->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) { if (vol->extra_flags & VOLUME_EXTRA_FLAGS_IGNORE_UNIXPRIVS) { struct stat stbuf; /* See if the file exists */ ret=ll_getattr(vol,path,&stbuf,0); return ret; } return -ENOSYS; }; get_dirid(vol,converted_path,basename,&dirid ); if ((rc=get_unixprivs(vol, dirid,basename, &fp))) return rc; #if 0 FIXME set_uidgid(volume,&fp,uid,gid); THIS IS the wrong set of returns to check... #endif rc=set_unixprivs(vol, dirid, basename, &fp); switch(rc) { case -ENOSYS: return -ENOSYS; case kFPNoErr: break; case kFPAccessDenied: return -EACCES; case kFPObjectNotFound: return -ENOENT; case kFPBitmapErr: case kFPMiscErr: case kFPObjectTypeErr: case kFPParamErr: default: break; } return 0; }
int ml_chmod(struct afp_volume * vol, const char * path, mode_t mode) { /* chmod has an interesting story to it. It is known to work with Darwin 10.3.9 (AFP 3.1), 10.4.2 and 10.5.x (AFP 3.2). chmod will not work properly in the following situations: - AFP 2.2, this needs some more verification but I don't see how it is possible - netatalk 2.0.3 and probably earlier: . netatalk will only enable it at all if you have "options=upriv" set for that volume. . netatalk will never be able to chmod the execute bit and some others on files; this is hard coded in unix.c's setfilemode() in 2.0.3. It's like it has 2.2 behaviour even though it is trying to speak 3.1. . The only bits allowed are S_IRUSR |S_IWUSR | S_IRGRP | S_IWGRP |S_IROTH | S_IWOTH; There's probably a reason for this, I don't know what it is. . afpfs-ng's behaviour's the same as the Darwin client. The right way to see if a volume supports chmod is to check the attributes found with getvolparm or volopen, then to test chmod the first time. */ int ret=0,rc; struct afp_file_info fp; unsigned int dirid; char basename[AFP_MAX_PATH]; char converted_path[AFP_MAX_PATH]; uid_t uid; gid_t gid; if (invalid_filename(vol->server,path)) return -ENAMETOOLONG; if (volume_is_readonly(vol)) return -EACCES; /* There's no way to do this in AFP < 3.0 */ if (~ vol->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) { if (vol->extra_flags & VOLUME_EXTRA_FLAGS_IGNORE_UNIXPRIVS) { struct stat stbuf; /* See if the file exists */ ret=ll_getattr(vol,path,&stbuf,0); return ret; } return -ENOSYS; }; if (convert_path_to_afp(vol->server->path_encoding, converted_path,(char *) path,AFP_MAX_PATH)) { return -EINVAL; } ret=appledouble_chmod(vol,path,mode); if (ret<0) return ret; if (ret==1) return 0; get_dirid(vol,converted_path,basename,&dirid ); if ((rc=get_unixprivs(vol, dirid,basename, &fp))) return rc; mode&=(~S_IFDIR); /* Don't bother updating it if it's already the same */ if ((fp.unixprivs.permissions&(~S_IFDIR))==mode) return 0; /* Check to make sure that we can; some servers (at least netatalk) don't report an error when you try to setfileparm when you don't own the file. */ /* Try to guess if the operation is possible */ uid=fp.unixprivs.uid; gid=fp.unixprivs.gid; if (translate_uidgid_to_client(vol, &uid,&gid)) return -EIO; if ((gid!=getgid()) && (uid!=geteuid())) { return -EPERM; } fp.unixprivs.permissions=mode; rc=set_unixprivs(vol, dirid,basename, &fp); if (rc==-ENOSYS) { return -ENOSYS; } return -ret; }
void appimage_set(const char *appimage) { assert(appimage); assert(devloop == NULL); // don't call this twice! EUID_ASSERT(); #ifdef LOOP_CTL_GET_FREE // test for older kernels; this definition is found in /usr/include/linux/loop.h // check appimage file invalid_filename(appimage); if (access(appimage, R_OK) == -1) { fprintf(stderr, "Error: cannot access AppImage file\n"); exit(1); } // get appimage type and ELF size // a value of 0 means we are dealing with a type1 appimage long unsigned int size = appimage2_size(appimage); if (arg_debug) printf("AppImage ELF size %lu\n", size); // open appimage file /* coverity[toctou] */ int ffd = open(appimage, O_RDONLY|O_CLOEXEC); if (ffd == -1) { fprintf(stderr, "Error: cannot open AppImage file\n"); exit(1); } // find or allocate a free loop device to use EUID_ROOT(); int cfd = open("/dev/loop-control", O_RDWR); if (cfd == -1) { fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n"); exit(1); } int devnr = ioctl(cfd, LOOP_CTL_GET_FREE); if (devnr == -1) { fprintf(stderr, "Error: cannot allocate a new loopback device\n"); exit(1); } close(cfd); if (asprintf(&devloop, "/dev/loop%d", devnr) == -1) errExit("asprintf"); int lfd = open(devloop, O_RDONLY); if (lfd == -1) { fprintf(stderr, "Error: cannot open %s\n", devloop); exit(1); } if (ioctl(lfd, LOOP_SET_FD, ffd) == -1) { fprintf(stderr, "Error: cannot configure the loopback device\n"); exit(1); } if (size) { struct loop_info64 info; memset(&info, 0, sizeof(struct loop_info64)); info.lo_offset = size; if (ioctl(lfd, LOOP_SET_STATUS64, &info) == -1) errExit("configure appimage offset"); } close(lfd); close(ffd); EUID_USER(); // creates appimage mount point perms 0700 if (asprintf(&mntdir, "%s/.appimage-%u", RUN_FIREJAIL_APPIMAGE_DIR, getpid()) == -1) errExit("asprintf"); EUID_ROOT(); mkdir_attr(mntdir, 0700, getuid(), getgid()); EUID_USER(); // mount char *mode; if (asprintf(&mode, "mode=700,uid=%d,gid=%d", getuid(), getgid()) == -1) errExit("asprintf"); EUID_ROOT(); if (size == 0) { if (mount(devloop, mntdir, "iso9660",MS_MGC_VAL|MS_RDONLY, mode) < 0) errExit("mounting appimage"); } else { if (mount(devloop, mntdir, "squashfs",MS_MGC_VAL|MS_RDONLY, mode) < 0) errExit("mounting appimage"); } if (arg_debug) printf("appimage mounted on %s\n", mntdir); EUID_USER(); // set environment if (setenv("APPIMAGE", appimage, 1) < 0) errExit("setenv"); if (mntdir && setenv("APPDIR", mntdir, 1) < 0) errExit("setenv"); // build new command line if (asprintf(&cfg.command_line, "%s/AppRun", mntdir) == -1) errExit("asprintf"); free(mode); #ifdef HAVE_GCOV __gcov_flush(); #endif #else fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n"); exit(1); #endif }
void check_output(int argc, char **argv) { int i; char *outfile = NULL; // drop_privs(0); int found = 0; for (i = 1; i < argc; i++) { if (strncmp(argv[i], "--output=", 9) == 0) { found = 1; invalid_filename(argv[i] + 9); outfile = argv[i] + 9; // do not accept directories, links, and files with ".." if (strstr(outfile, "..") || is_link(outfile) || is_dir(outfile)) { fprintf(stderr, "Error: invalid output file. Links, directories and files with \"..\" are not allowed.\n"); exit(1); } struct stat s; if (stat(outfile, &s) == 0) { // check permissions if (s.st_uid != getuid() || s.st_gid != getgid()) { fprintf(stderr, "Error: the output file needs to be owned by the current user.\n"); exit(1); } // check hard links if (s.st_nlink != 1) { fprintf(stderr, "Error: no hard links allowed.\n"); exit(1); } } // drop privileges and try to open the file for writing drop_privs(0); /* coverity[toctou] */ FILE *fp = fopen(outfile, "a"); if (!fp) { fprintf(stderr, "Error: cannot open output file %s\n", outfile); exit(1); } fclose(fp); break; } } if (!found) return; // build the new command line int len = 0; for (i = 0; i < argc; i++) { len += strlen(argv[i]) + 1; // + ' ' } len += 50 + strlen(outfile); // tee command char *cmd = malloc(len + 1); // + '\0' if (!cmd) errExit("malloc"); char *ptr = cmd; for (i = 0; i < argc; i++) { if (strncmp(argv[i], "--output=", 9) == 0) continue; ptr += sprintf(ptr, "%s ", argv[i]); } sprintf(ptr, "| %s/firejail/ftee %s", LIBDIR, outfile); // run command char *a[4]; a[0] = "/bin/bash"; a[1] = "-c"; a[2] = cmd; a[3] = NULL; execvp(a[0], a); perror("execvp"); exit(1); }