github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/cmd/libsnap-confine-private/snap.c (about)

     1  /*
     2   * Copyright (C) 2015 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 "snap.h"
    19  
    20  #include <errno.h>
    21  #include <regex.h>
    22  #include <stddef.h>
    23  #include <stdlib.h>
    24  #include <string.h>
    25  #include <ctype.h>
    26  
    27  #include "utils.h"
    28  #include "string-utils.h"
    29  #include "cleanup-funcs.h"
    30  
    31  bool sc_security_tag_validate(const char *security_tag, const char *snap_name)
    32  {
    33  	/* Don't even check overly long tags. */
    34  	if (strlen(security_tag) > SNAP_SECURITY_TAG_MAX_LEN) {
    35  		return false;
    36  	}
    37  	const char *whitelist_re =
    38  	    "^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z0-9])*)$";
    39  	regex_t re;
    40  	if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0)
    41  		die("can not compile regex %s", whitelist_re);
    42  
    43  	// first capture is for verifying the full security tag, second capture
    44  	// for verifying the snap_name is correct for this security tag
    45  	regmatch_t matches[2];
    46  	int status =
    47  	    regexec(&re, security_tag, sizeof matches / sizeof *matches,
    48  		    matches, 0);
    49  	regfree(&re);
    50  
    51  	// Fail if no match or if snap name wasn't captured in the 2nd match group
    52  	if (status != 0 || matches[1].rm_so < 0) {
    53  		return false;
    54  	}
    55  
    56  	size_t len = matches[1].rm_eo - matches[1].rm_so;
    57  	return len == strlen(snap_name)
    58  	    && strncmp(security_tag + matches[1].rm_so, snap_name, len) == 0;
    59  }
    60  
    61  bool sc_is_hook_security_tag(const char *security_tag)
    62  {
    63  	const char *whitelist_re =
    64  	    "^snap\\.[a-z](-?[a-z0-9])*(_[a-z0-9]{1,10})?\\.(hook\\.[a-z](-?[a-z])*)$";
    65  
    66  	regex_t re;
    67  	if (regcomp(&re, whitelist_re, REG_EXTENDED | REG_NOSUB) != 0)
    68  		die("can not compile regex %s", whitelist_re);
    69  
    70  	int status = regexec(&re, security_tag, 0, NULL, 0);
    71  	regfree(&re);
    72  
    73  	return status == 0;
    74  }
    75  
    76  static int skip_lowercase_letters(const char **p)
    77  {
    78  	int skipped = 0;
    79  	for (const char *c = *p; *c >= 'a' && *c <= 'z'; ++c) {
    80  		skipped += 1;
    81  	}
    82  	*p = (*p) + skipped;
    83  	return skipped;
    84  }
    85  
    86  static int skip_digits(const char **p)
    87  {
    88  	int skipped = 0;
    89  	for (const char *c = *p; *c >= '0' && *c <= '9'; ++c) {
    90  		skipped += 1;
    91  	}
    92  	*p = (*p) + skipped;
    93  	return skipped;
    94  }
    95  
    96  static int skip_one_char(const char **p, char c)
    97  {
    98  	if (**p == c) {
    99  		*p += 1;
   100  		return 1;
   101  	}
   102  	return 0;
   103  }
   104  
   105  void sc_instance_name_validate(const char *instance_name, sc_error ** errorp)
   106  {
   107  	// NOTE: This function should be synchronized with the two other
   108  	// implementations: validate_instance_name and snap.ValidateInstanceName.
   109  	sc_error *err = NULL;
   110  
   111  	// Ensure that name is not NULL
   112  	if (instance_name == NULL) {
   113  		err =
   114  		    sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
   115  				  "snap instance name cannot be NULL");
   116  		goto out;
   117  	}
   118  	// instance name length + 1 extra overflow + 1 NULL
   119  	char s[SNAP_INSTANCE_LEN + 1 + 1] = { 0 };
   120  	strncpy(s, instance_name, sizeof(s) - 1);
   121  
   122  	char *t = s;
   123  	const char *snap_name = strsep(&t, "_");
   124  	const char *instance_key = strsep(&t, "_");
   125  	const char *third_separator = strsep(&t, "_");
   126  	if (third_separator != NULL) {
   127  		err =
   128  		    sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
   129  				  "snap instance name can contain only one underscore");
   130  		goto out;
   131  	}
   132  
   133  	sc_snap_name_validate(snap_name, &err);
   134  	if (err != NULL) {
   135  		goto out;
   136  	}
   137  	// When the instance_name is a normal snap name, instance_key will be
   138  	// NULL, so only validate instance_key when we found one.
   139  	if (instance_key != NULL) {
   140  		sc_instance_key_validate(instance_key, &err);
   141  	}
   142  
   143   out:
   144  	sc_error_forward(errorp, err);
   145  }
   146  
   147  void sc_instance_key_validate(const char *instance_key, sc_error ** errorp)
   148  {
   149  	// NOTE: see snap.ValidateInstanceName for reference of a valid instance key
   150  	// format
   151  	sc_error *err = NULL;
   152  
   153  	// Ensure that name is not NULL
   154  	if (instance_key == NULL) {
   155  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   156  				    "instance key cannot be NULL");
   157  		goto out;
   158  	}
   159  	// This is a regexp-free routine hand-coding the following pattern:
   160  	//
   161  	// "^[a-z]{1,10}$"
   162  	//
   163  	// The only motivation for not using regular expressions is so that we don't
   164  	// run untrusted input against a potentially complex regular expression
   165  	// engine.
   166  	int i = 0;
   167  	for (i = 0; instance_key[i] != '\0'; i++) {
   168  		if (islower(instance_key[i]) || isdigit(instance_key[i])) {
   169  			continue;
   170  		}
   171  		err =
   172  		    sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
   173  				  "instance key must use lower case letters or digits");
   174  		goto out;
   175  	}
   176  
   177  	if (i == 0) {
   178  		err =
   179  		    sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
   180  				  "instance key must contain at least one letter or digit");
   181  	} else if (i > SNAP_INSTANCE_KEY_LEN) {
   182  		err =
   183  		    sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
   184  				  "instance key must be shorter than 10 characters");
   185  	}
   186   out:
   187  	sc_error_forward(errorp, err);
   188  }
   189  
   190  void sc_snap_name_validate(const char *snap_name, sc_error ** errorp)
   191  {
   192  	// NOTE: This function should be synchronized with the two other
   193  	// implementations: validate_snap_name and snap.ValidateName.
   194  	sc_error *err = NULL;
   195  
   196  	// Ensure that name is not NULL
   197  	if (snap_name == NULL) {
   198  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   199  				    "snap name cannot be NULL");
   200  		goto out;
   201  	}
   202  	// This is a regexp-free routine hand-codes the following pattern:
   203  	//
   204  	// "^([a-z0-9]+-?)*[a-z](-?[a-z0-9])*$"
   205  	//
   206  	// The only motivation for not using regular expressions is so that we
   207  	// don't run untrusted input against a potentially complex regular
   208  	// expression engine.
   209  	const char *p = snap_name;
   210  	if (skip_one_char(&p, '-')) {
   211  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   212  				    "snap name cannot start with a dash");
   213  		goto out;
   214  	}
   215  	bool got_letter = false;
   216  	int n = 0, m;
   217  	for (; *p != '\0';) {
   218  		if ((m = skip_lowercase_letters(&p)) > 0) {
   219  			n += m;
   220  			got_letter = true;
   221  			continue;
   222  		}
   223  		if ((m = skip_digits(&p)) > 0) {
   224  			n += m;
   225  			continue;
   226  		}
   227  		if (skip_one_char(&p, '-') > 0) {
   228  			n++;
   229  			if (*p == '\0') {
   230  				err =
   231  				    sc_error_init(SC_SNAP_DOMAIN,
   232  						  SC_SNAP_INVALID_NAME,
   233  						  "snap name cannot end with a dash");
   234  				goto out;
   235  			}
   236  			if (skip_one_char(&p, '-') > 0) {
   237  				err =
   238  				    sc_error_init(SC_SNAP_DOMAIN,
   239  						  SC_SNAP_INVALID_NAME,
   240  						  "snap name cannot contain two consecutive dashes");
   241  				goto out;
   242  			}
   243  			continue;
   244  		}
   245  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   246  				    "snap name must use lower case letters, digits or dashes");
   247  		goto out;
   248  	}
   249  	if (!got_letter) {
   250  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   251  				    "snap name must contain at least one letter");
   252  		goto out;
   253  	}
   254  	if (n < 2) {
   255  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   256  				    "snap name must be longer than 1 character");
   257  		goto out;
   258  	}
   259  	if (n > SNAP_NAME_LEN) {
   260  		err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
   261  				    "snap name must be shorter than 40 characters");
   262  		goto out;
   263  	}
   264  
   265   out:
   266  	sc_error_forward(errorp, err);
   267  }
   268  
   269  void sc_snap_drop_instance_key(const char *instance_name, char *snap_name,
   270  			       size_t snap_name_size)
   271  {
   272  	sc_snap_split_instance_name(instance_name, snap_name, snap_name_size,
   273  				    NULL, 0);
   274  }
   275  
   276  void sc_snap_split_instance_name(const char *instance_name, char *snap_name,
   277  				 size_t snap_name_size, char *instance_key,
   278  				 size_t instance_key_size)
   279  {
   280  	if (instance_name == NULL) {
   281  		die("internal error: cannot split instance name when it is unset");
   282  	}
   283  	if (snap_name == NULL && instance_key == NULL) {
   284  		die("internal error: cannot split instance name when both snap name and instance key are unset");
   285  	}
   286  
   287  	const char *pos = strchr(instance_name, '_');
   288  	const char *instance_key_start = "";
   289  	size_t snap_name_len = 0;
   290  	size_t instance_key_len = 0;
   291  	if (pos == NULL) {
   292  		snap_name_len = strlen(instance_name);
   293  	} else {
   294  		snap_name_len = pos - instance_name;
   295  		instance_key_start = pos + 1;
   296  		instance_key_len = strlen(instance_key_start);
   297  	}
   298  
   299  	if (snap_name != NULL) {
   300  		if (snap_name_len >= snap_name_size) {
   301  			die("snap name buffer too small");
   302  		}
   303  
   304  		memcpy(snap_name, instance_name, snap_name_len);
   305  		snap_name[snap_name_len] = '\0';
   306  	}
   307  
   308  	if (instance_key != NULL) {
   309  		if (instance_key_len >= instance_key_size) {
   310  			die("instance key buffer too small");
   311  		}
   312  		memcpy(instance_key, instance_key_start, instance_key_len);
   313  		instance_key[instance_key_len] = '\0';
   314  	}
   315  }