github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-confine/seccomp-support.c (about)

     1  /*
     2   * Copyright (C) 2015-2017 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  #include "config.h"
    18  #include "seccomp-support.h"
    19  
    20  #include <errno.h>
    21  #include <fcntl.h>
    22  #include <limits.h>
    23  #include <stdio.h>
    24  #include <string.h>
    25  #include <sys/prctl.h>
    26  #include <sys/stat.h>
    27  #include <sys/syscall.h>
    28  #include <sys/types.h>
    29  #include <unistd.h>
    30  
    31  #include <linux/filter.h>
    32  #include <linux/seccomp.h>
    33  
    34  #include "../libsnap-confine-private/cleanup-funcs.h"
    35  #include "../libsnap-confine-private/secure-getenv.h"
    36  #include "../libsnap-confine-private/string-utils.h"
    37  #include "../libsnap-confine-private/utils.h"
    38  
    39  #include "seccomp-support-ext.h"
    40  
    41  static const char *filter_profile_dir = "/var/lib/snapd/seccomp/bpf/";
    42  
    43  // MAX_BPF_SIZE is an arbitrary limit.
    44  #define MAX_BPF_SIZE (32 * 1024)
    45  
    46  typedef struct sock_filter bpf_instr;
    47  
    48  static void validate_path_has_strict_perms(const char *path)
    49  {
    50  	struct stat stat_buf;
    51  	if (stat(path, &stat_buf) < 0) {
    52  		die("cannot stat %s", path);
    53  	}
    54  
    55  	errno = 0;
    56  	if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0) {
    57  		die("%s not root-owned %i:%i", path, stat_buf.st_uid,
    58  		    stat_buf.st_gid);
    59  	}
    60  
    61  	if (stat_buf.st_mode & S_IWOTH) {
    62  		die("%s has 'other' write %o", path, stat_buf.st_mode);
    63  	}
    64  }
    65  
    66  static void validate_bpfpath_is_safe(const char *path)
    67  {
    68  	if (path == NULL || strlen(path) == 0 || path[0] != '/') {
    69  		die("valid_bpfpath_is_safe needs an absolute path as input");
    70  	}
    71  	// strtok_r() modifies its first argument, so work on a copy
    72  	char *tokenized SC_CLEANUP(sc_cleanup_string) = NULL;
    73  	tokenized = sc_strdup(path);
    74  	// allocate a string large enough to hold path, and initialize it to
    75  	// '/'
    76  	size_t checked_path_size = strlen(path) + 1;
    77  	char *checked_path SC_CLEANUP(sc_cleanup_string) = NULL;
    78  	checked_path = calloc(checked_path_size, 1);
    79  	if (checked_path == NULL) {
    80  		die("cannot allocate memory for checked_path");
    81  	}
    82  
    83  	checked_path[0] = '/';
    84  	checked_path[1] = '\0';
    85  
    86  	// validate '/'
    87  	validate_path_has_strict_perms(checked_path);
    88  
    89  	// strtok_r needs a pointer to keep track of where it is in the
    90  	// string.
    91  	char *buf_saveptr = NULL;
    92  
    93  	// reconstruct the path from '/' down to profile_name
    94  	char *buf_token = strtok_r(tokenized, "/", &buf_saveptr);
    95  	while (buf_token != NULL) {
    96  		char *prev SC_CLEANUP(sc_cleanup_string) = NULL;
    97  		prev = sc_strdup(checked_path);	// needed by vsnprintf in sc_must_snprintf
    98  		// append '<buf_token>' if checked_path is '/', otherwise '/<buf_token>'
    99  		if (strlen(checked_path) == 1) {
   100  			sc_must_snprintf(checked_path, checked_path_size,
   101  					 "%s%s", prev, buf_token);
   102  		} else {
   103  			sc_must_snprintf(checked_path, checked_path_size,
   104  					 "%s/%s", prev, buf_token);
   105  		}
   106  		validate_path_has_strict_perms(checked_path);
   107  
   108  		buf_token = strtok_r(NULL, "/", &buf_saveptr);
   109  	}
   110  }
   111  
   112  bool sc_apply_seccomp_profile_for_security_tag(const char *security_tag)
   113  {
   114  	debug("loading bpf program for security tag %s", security_tag);
   115  
   116  	char profile_path[PATH_MAX] = { 0 };
   117  	sc_must_snprintf(profile_path, sizeof(profile_path), "%s/%s.bin",
   118  			 filter_profile_dir, security_tag);
   119  
   120  	// Wait some time for the security profile to show up. When
   121  	// the system boots snapd will created security profiles, but
   122  	// a service snap (e.g. network-manager) starts in parallel with
   123  	// snapd so for such snaps, the profiles may not be generated
   124  	// yet
   125  	long max_wait = 120;
   126  	const char *MAX_PROFILE_WAIT = getenv("SNAP_CONFINE_MAX_PROFILE_WAIT");
   127  	if (MAX_PROFILE_WAIT != NULL) {
   128  		char *endptr = NULL;
   129  		errno = 0;
   130  		long env_max_wait = strtol(MAX_PROFILE_WAIT, &endptr, 10);
   131  		if (errno != 0 || MAX_PROFILE_WAIT == endptr || *endptr != '\0'
   132  		    || env_max_wait <= 0) {
   133  			die("SNAP_CONFINE_MAX_PROFILE_WAIT invalid");
   134  		}
   135  		max_wait = env_max_wait > 0 ? env_max_wait : max_wait;
   136  	}
   137  	if (max_wait > 3600) {
   138  		max_wait = 3600;
   139  	}
   140  	for (long i = 0; i < max_wait; ++i) {
   141  		if (access(profile_path, F_OK) == 0) {
   142  			break;
   143  		}
   144  		sleep(1);
   145  	}
   146  
   147  	// TODO: move over to open/openat as an additional hardening measure.
   148  
   149  	// validate '/' down to profile_path are root-owned and not
   150  	// 'other' writable to avoid possibility of privilege
   151  	// escalation via bpf program load when paths are incorrectly
   152  	// set on the system.
   153  	validate_bpfpath_is_safe(profile_path);
   154  
   155      /* The extra space has dual purpose. First of all, it is required to detect
   156       * feof() while still being able to correctly read MAX_BPF_SIZE bytes of
   157       * seccomp profile.  In addition, because we treat the profile as a
   158       * quasi-string and use sc_streq(), to compare it. The extra space is used
   159       * as a way to ensure the result is a terminated string (though in practice
   160       * it can contain embedded NULs any earlier position). Note that
   161       * sc_read_seccomp_filter knows about the extra space and ensures that the
   162       * buffer is never empty. */
   163  	char bpf[MAX_BPF_SIZE + 1] = { 0 };
   164  	size_t num_read = sc_read_seccomp_filter(profile_path, bpf, sizeof bpf);
   165  	if (sc_streq(bpf, "@unrestricted\n")) {
   166  		return false;
   167  	}
   168  	struct sock_fprog prog = {
   169  		.len = num_read / sizeof(struct sock_filter),
   170  		.filter = (struct sock_filter *)bpf,
   171  	};
   172  	sc_apply_seccomp_filter(&prog);
   173  	return true;
   174  }
   175  
   176  void sc_apply_global_seccomp_profile(void)
   177  {
   178  	const char *profile_path = "/var/lib/snapd/seccomp/bpf/global.bin";
   179  	/* The profile may be absent. */
   180  	if (access(profile_path, F_OK) != 0) {
   181  		return;
   182  	}
   183  	// TODO: move over to open/openat as an additional hardening measure.
   184  	validate_bpfpath_is_safe(profile_path);
   185  
   186  	char bpf[MAX_BPF_SIZE + 1] = { 0 };
   187  	size_t num_read = sc_read_seccomp_filter(profile_path, bpf, sizeof bpf);
   188  	struct sock_fprog prog = {
   189  		.len = num_read / sizeof(struct sock_filter),
   190  		.filter = (struct sock_filter *)bpf,
   191  	};
   192  	sc_apply_seccomp_filter(&prog);
   193  }