Exemplo n.º 1
0
scmp_filter_ctx sc_prepare_seccomp_context(const char *filter_profile)
{
	int rc = 0;
	scmp_filter_ctx ctx = NULL;
	FILE *f = NULL;
	size_t lineno = 0;
	uid_t real_uid, effective_uid, saved_uid;
	struct preprocess pre;
	struct seccomp_args sargs;

	debug("preparing seccomp profile associated with security tag %s",
	      filter_profile);

	// initialize hsearch map
	sc_map_init();

	ctx = seccomp_init(SCMP_ACT_KILL);
	if (ctx == NULL) {
		errno = ENOMEM;
		die("seccomp_init() failed");
	}
	// Setup native arch and any compatibility archs
	sc_add_seccomp_archs(ctx);

	// Disable NO_NEW_PRIVS because it interferes with exec transitions in
	// AppArmor. Unfortunately this means that security policies must be
	// very careful to not allow the following otherwise apps can escape
	// the sandbox:
	//   - seccomp syscall
	//   - prctl with PR_SET_SECCOMP
	//   - ptrace (trace) in AppArmor
	//   - capability sys_admin in AppArmor
	// Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to
	// change the seccomp sandbox.

	if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0)
		die("could not find user IDs");

	// If running privileged or capable of raising, disable nnp
	if (real_uid == 0 || effective_uid == 0 || saved_uid == 0)
		if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0) != 0)
			die("Cannot disable nnp");

	// Note that secure_gettenv will always return NULL when suid, so
	// SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR can't be (ab)used in that case.
	if (secure_getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL)
		filter_profile_dir =
		    secure_getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR");

	char profile_path[512];	// arbitrary path name limit
	must_snprintf(profile_path, sizeof(profile_path), "%s/%s",
		      filter_profile_dir, filter_profile);

	f = fopen(profile_path, "r");
	if (f == NULL) {
		fprintf(stderr, "Can not open %s (%s)\n", profile_path,
			strerror(errno));
		die("aborting");
	}
	// Note, preprocess_filter() die()s on error
	preprocess_filter(f, &pre);

	if (pre.unrestricted) {
		seccomp_release(ctx);
		ctx = NULL;
		goto out;
	}
	// FIXME: right now complain mode is the equivalent to unrestricted.
	// We'll want to change this once we seccomp logging is in order.
	if (pre.complain) {
		seccomp_release(ctx);
		ctx = NULL;
		goto out;
	}

	char buf[SC_MAX_LINE_LENGTH];
	while (fgets(buf, sizeof(buf), f) != NULL) {
		lineno++;

		// skip policy-irrelevant lines
		if (validate_and_trim_line(buf, sizeof(buf), lineno) == 0)
			continue;

		char *buf_copy = strdup(buf);
		if (buf_copy == NULL)
			die("Out of memory");

		int pr_rc = parse_line(buf_copy, &sargs);
		free(buf_copy);
		if (pr_rc != PARSE_OK) {
			// as this is a syscall whitelist an invalid syscall
			// is ok and the error can be ignored
			if (pr_rc == PARSE_INVALID_SYSCALL)
				continue;
			die("could not parse line");
		}

		rc = seccomp_rule_add_exact_array(ctx, SCMP_ACT_ALLOW,
						  sargs.syscall_nr,
						  sargs.length, sargs.arg_cmp);
		if (rc != 0) {
			rc = seccomp_rule_add_array(ctx, SCMP_ACT_ALLOW,
						    sargs.syscall_nr,
						    sargs.length,
						    sargs.arg_cmp);
			if (rc != 0) {
				fprintf(stderr,
					"seccomp_rule_add_array failed with %i for '%s'\n",
					rc, buf);
				errno = 0;
				die("aborting");
			}
		}
	}

 out:
	if (f != NULL) {
		if (fclose(f) != 0)
			die("could not close seccomp file");
	}
	sc_map_destroy();
	return ctx;
}
int main(int argc, char *argv[])
{
  const char *argv0 = argv[0];
  int opt, optidx;

  bool separate_read_write = false;

  bool proxy = false;
  struct sockaddr_in proxy_sin;

  bool join = false;

#ifdef HAVE_SECCOMP
  int nrules = 0;
  uint32_t def_action = SCMP_ACT_ALLOW;
  char *fixed = (char *)mmap((void *)0x800000, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
#endif

  struct option longopts[] = {
    {"abstract"    , no_argument       , 0 , 'a'} ,
    {"directory"   , required_argument , 0 , 'd'} ,
    {"environment" , required_argument , 0 , 'e'} ,
    {"join"        , no_argument       , 0 , 'j'} ,
    {"policy"      , required_argument , 0 , 'o'} ,
    {"proxy"       , required_argument , 0 , 'p'} ,
    {"seccomp"     , required_argument , 0 , 'c'} ,
    {"separate"    , no_argument       , 0 , 's'} ,
    {0             , 0                 , 0 , 0}   ,
  };
  while ((opt = getopt_long(argc, argv, "+0ac:d:e:hjo:p:s", longopts, &optidx)) != -1) {
    switch (opt) {
    case 'a':
      af_unix = true;
      break;
    case 'c': {
#ifdef HAVE_SECCOMP
      char *pp = optarg, *p, *qq, *q, *saved0, *saved1;
      for (; ; pp = NULL) {
        p = strtok_r(pp, " \t,", &saved0);
        if (! p) break;

        bool negative = false;
        int num, pos, nfilters = 0;
        enum scmp_compare op;
        if (nrules >= SIZE(scmp_rules))
          errx(BAD_ARG, "too many rules");
        if (*p == '+')
          p++;
        else if (*p == '-')
          p++, negative = true;
        scmp_rules[nrules].action = negative ? SCMP_ACT_TRAP : SCMP_ACT_ALLOW;

        for (qq = p; ; qq = NULL) {
          q = strtok_r(qq, ":", &saved1);
          if (! q) break;
          if (qq) {
            num = seccomp_syscall_resolve_name(q);
            if (num < 0)
              errx(BAD_ARG, "unknown syscall \"%s\"", q);
            scmp_rules[nrules].syscall = num;
          } else {
            if (nfilters >= SIZE(scmp_rules[0].arg_array))
              errx(BAD_ARG, "too many filters");
            if (! ('0' <= *q && *q <= '5'))
              errx(BAD_ARG, "argument position should be 0-5");
            pos = *q-'0';
            REP(i, nfilters)
              if (pos == scmp_rules[nrules].arg_array[i].arg)
                errx(BAD_ARG, "duplicate argument position %d", pos);
            q++;
            if (*q == '=')
              q++, op = SCMP_CMP_EQ;
            else if (*q == '!' && q[1] == '=')
              q += 2, op = SCMP_CMP_NE;
            else if (*q == '<') {
              if (q[1] == '=')
                q += 2, op = SCMP_CMP_LE;
              else
                q++, op = SCMP_CMP_LT;
            } else if (*q == '>') {
              if (q[1] == '=')
                q += 2, op = SCMP_CMP_GE;
              else
                q++, op = SCMP_CMP_GT;
            }
            else
              errx(BAD_ARG, "unknown operator \"%c\"", *q);
            scmp_datum_t val = strtol(q, &q, 0);
            if (*q)
              errx(BAD_ARG, "invalid number");
            scmp_rules[nrules].arg_array[nfilters++] = SCMP_CMP(pos, op, val);
          }
        }
        scmp_rules[nrules].arg_cnt = nfilters;
        nrules++;
      }
#else
      errx(ERR_SYSCALL, "HAVE_SECCOMP not enabled");
#endif
      break;
    }
    case 'd':
      Chdir(optarg);
      break;
    case 'h':
      show_help(STDOUT_FILENO, argv0);
      break;
    case 'j':
      join = true;
      break;
    case 'e':
      putenv(optarg);
      break;
    case 'o':
#ifdef HAVE_SECCOMP
      if (optarg[0] == 'k')
        def_action = SCMP_ACT_KILL;
      else if (optarg[0] == 't')
        def_action = SCMP_ACT_TRAP;
      else if (optarg[0] == 'a')
        def_action = SCMP_ACT_ALLOW;
#else
      errx(ERR_SYSCALL, "HAVE_SECCOMP not enabled");
#endif
      break;
    case 'p': // proxy
      {
        char *p = strchr(optarg, ':');
        if (! p)
          errx(BAD_ARG, "no semicolon");
        *p = '\0';
        proxy_sin.sin_family = AF_INET;
        if (inet_aton(optarg, &proxy_sin.sin_addr) < 0)
          errx(BAD_ARG, "gethostbyname: %s", strerror(errno));
        proxy_sin.sin_port = htons(strtol(p+1, &p, 10));
        if (*p)
          errx(BAD_ARG, "port");
        proxy = true;
      }
      break;
    case 's':
      separate_read_write = true;
      break;
    default:
      show_help(STDERR_FILENO, argv0);
      break;
    }
  }

  argc -= optind;
  argv += optind;

  if (argc < 1) {
    show_help(STDERR_FILENO, argv0);
    return BAD_ARG;
  }

  char buf[4096];
  int pipe_p2c[2], pipe_c2p1[2], pipe_c2p2[2];
  if (af_unix) {
    unix_p2c.sun_family = AF_UNIX; 
    unix_c2p1.sun_family = AF_UNIX;
    unix_c2p2.sun_family = AF_UNIX;
    unix_p2c.sun_path[0] = '\0';
    unix_c2p1.sun_path[0] = '\0';
    unix_c2p2.sun_path[0] = '\0';
    sprintf(unix_p2c.sun_path+1, "/tmp/unix/%d-%d.in", getuid(), getpid());
    sprintf(unix_c2p1.sun_path+1, "/tmp/unix/%d-%d.out", getuid(), getpid());
    sprintf(unix_c2p2.sun_path+1, "/tmp/unix/%d-%d.err", getuid(), getpid());
    pipe_p2c[0] = socket(AF_UNIX, SOCK_STREAM, 0);
    pipe_c2p1[1] = socket(AF_UNIX, SOCK_STREAM, 0);
    if (! join)
      pipe_c2p2[1] = socket(AF_UNIX, SOCK_STREAM, 0);
    atexit(atexit_rm);
    if (bind(pipe_p2c[0], &unix_p2c, sizeof unix_p2c) < 0 ||
        bind(pipe_c2p1[1], &unix_c2p1, sizeof unix_c2p1) < 0)
      return perror(""), 0;
    if (! join && bind(pipe_c2p2[1], &unix_c2p2, sizeof unix_c2p2) < 0)
      return perror(""), 0;
    if (listen(pipe_p2c[0], 1) < 0 ||
        listen(pipe_c2p1[1], 1) < 0)
      return perror(""), 0;
    if (! join && listen(pipe_c2p2[1], 1) < 0)
      return perror(""), 0;
  } else {
    Pipe(pipe_p2c);
    Pipe(pipe_c2p1);
    if (! join)
      Pipe(pipe_c2p2);
  }

  child = Fork();
  if (! child) {
    // child
    if (af_unix) {
      // close bound sockets
      close(pipe_p2c[0]);
      close(pipe_c2p1[1]);
      close(pipe_c2p2[1]);

      // domain sockets for client
      pipe_p2c[1] = socket(AF_UNIX, SOCK_STREAM, 0);
      pipe_c2p1[0] = socket(AF_UNIX, SOCK_STREAM, 0);
      if (! join)
        pipe_c2p2[0] = socket(AF_UNIX, SOCK_STREAM, 0);

      if (connect(pipe_p2c[1], &unix_p2c, sizeof unix_p2c) < 0 ||
          connect(pipe_c2p1[0], &unix_c2p1, sizeof unix_c2p1) < 0)
        return 0;
      dup2(pipe_p2c[1], STDIN_FILENO); close(pipe_p2c[1]);
      dup2(pipe_c2p1[0], STDOUT_FILENO); close(pipe_c2p1[0]);
      if (join)
        dup2(STDOUT_FILENO, STDERR_FILENO);
      else {
        connect(pipe_c2p2[0], &unix_c2p2, sizeof unix_c2p2);
        dup2(pipe_c2p2[0], STDERR_FILENO); close(pipe_c2p2[0]);
      }
    } else {
      close(pipe_p2c[1]); dup2(pipe_p2c[0], STDIN_FILENO); close(pipe_p2c[0]);
      close(pipe_c2p1[0]); dup2(pipe_c2p1[1], STDOUT_FILENO); close(pipe_c2p1[1]);
      if (join)
        dup2(STDOUT_FILENO, STDERR_FILENO);
      else {
        close(pipe_c2p2[0]); dup2(pipe_c2p2[1], STDERR_FILENO); close(pipe_c2p2[1]);
      }
    }

#ifdef HAVE_SECCOMP
    scmp_filter_ctx ctx = seccomp_init(def_action);
    if (ctx == NULL)
      return 1;
    REP(i, nrules) {
      int ret = seccomp_rule_add_array(ctx, scmp_rules[i].action, scmp_rules[i].syscall,
                                       scmp_rules[i].arg_cnt, scmp_rules[i].arg_array);
      if (ret < 0)
        return 1;
    }
    if (seccomp_load(ctx) < 0)
      return 1;
    seccomp_release(ctx);

    strcpy(fixed, *argv);
    execv(fixed, argv); // execve will change the first argument
#else
    execvp(*argv, argv);
#endif
  } else {
void pcm_json_rule_to_seccomp(json_t *rule)
{
	json_t *syscall, *action, *restrictions;
	int rc;
	int syscall_num;
	int arg_count;
	int i;
	int default_action;
	struct scmp_arg_cmp arg_cmp[ARG_COUNT_MAX];
		
		
	syscall = json_object_get(rule, "syscall");
	if(! json_is_string(syscall)) {
		errx(EXIT_FAILURE, "Expected string for system call");
	}

	action = json_object_get(rule, "action");
	if(! json_is_string(action)) {
		errx(EXIT_FAILURE, "Expected string for action value");
	}

	default_action = pcm_string_to_policy(json_string_value(action));
	if(default_action == -1) {
		errx(EXIT_FAILURE, "Rule action invalid (expecting ALLOW, KILL, or ERRNO)");
	}	

	syscall_num = seccomp_syscall_resolve_name(json_string_value(syscall));
	if(syscall_num == __NR_SCMP_ERROR) {
		errx(EXIT_FAILURE, "System call number is negative?");
	}

	arg_count = 0;
	restrictions = json_object_get(rule, "restrictions");

	if(restrictions) {
		if(! json_is_array(restrictions)) {
			errx(EXIT_FAILURE, "restrictions is not an array");
		}

		if(json_array_size(restrictions) > ARG_COUNT_MAX) {
			errx(EXIT_FAILURE, "too many restrictions :/");
		}

		for(i = 0; i < json_array_size(restrictions); i++) {
			json_t *arg_restriction;
			arg_restriction = json_array_get(restrictions, i);
			if(! arg_restriction) {
				errx(EXIT_FAILURE, "fetching entry from array failed?");
			}

			if(! json_is_object(arg_restriction)) {
				errx(EXIT_FAILURE, "argument restriction is not an object");
			}

			pcm_json_restriction_to_arg_cmp(arg_restriction, &arg_cmp[i]);
		}
		arg_count = i;
	}

	rc = seccomp_rule_add_array(PCM_GLOBAL.seccomp, default_action, syscall_num, arg_count, arg_cmp);
	if(rc < 0) {
		errx(EXIT_FAILURE, "Adding rule failed?");
	}


}