1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
|
/*
* Copyright (C) 2015 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "snap.h"
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <regex.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "cleanup-funcs.h"
#include "string-utils.h"
#include "utils.h"
bool sc_security_tag_validate(const char *security_tag, const char *snap_instance, const char *component_name) {
/* Don't even check overly long tags. */
if (strlen(security_tag) > SNAP_SECURITY_TAG_MAX_LEN) {
return false;
}
const char *whitelist_re =
"^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)(\\.[a-zA-Z0-9](-?[a-zA-Z0-9])*|(\\+([a-z0-9](-?[a-z0-9])*))?"
"\\.hook\\.[a-z](-?[a-z0-9])*)$";
regex_t re;
if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0) die("can not compile regex %s", whitelist_re);
// first capture is for verifying the full security tag, second capture
// for verifying the snap_name is correct for this security tag, eighth capture
// for verifying the component_name is correct for this security tag. the
// expression currently contains 9 capture groups, but we only care about these 3,
// which unfortunately are not within the first 3 submatches, but rather group 1,
// 2, and 7, so for completeness capture all the groups.
enum { num_matches = 9 };
regmatch_t matches[num_matches];
if (num_matches != re.re_nsub) {
die("internal error: all regex capture groups not fully accounted for");
}
int status = regexec(&re, security_tag, SC_ARRAY_SIZE(matches), matches, 0);
regfree(&re);
// Fail if no match or if snap name wasn't captured in the 2nd match group
if (status != 0 || matches[1].rm_so < 0) {
return false;
}
// if we expect a component name (a non-null string was passed in here),
// then we need to make sure that the regex captured a component name
if (component_name != NULL) {
// fail if the security tag doesn't contain a component name and we
// expected one
if (matches[7].rm_so < 0) {
return false;
}
size_t component_name_len = strlen(component_name);
// don't allow empty component names, only allow NULL as an indication
// that we don't expect a component name.
if (component_name_len == 0) {
return false;
}
size_t len = matches[7].rm_eo - matches[7].rm_so;
if (len != component_name_len || strncmp(security_tag + matches[7].rm_so, component_name, len) != 0) {
return false;
}
} else if (matches[7].rm_so >= 0) {
// fail if the security tag contains a component name and we didn't
// expect one
return false;
}
size_t len = matches[1].rm_eo - matches[1].rm_so;
return len == strlen(snap_instance) && strncmp(security_tag + matches[1].rm_so, snap_instance, len) == 0;
}
bool sc_is_hook_security_tag(const char *security_tag) {
const char *whitelist_re = "^snap\\.[a-z](-?[a-z0-9])*(_[a-z0-9]{1,10})?\\.(hook\\.[a-z](-?[a-z0-9])*)$";
regex_t re;
if (regcomp(&re, whitelist_re, REG_EXTENDED | REG_NOSUB) != 0) die("can not compile regex %s", whitelist_re);
int status = regexec(&re, security_tag, 0, NULL, 0);
regfree(&re);
return status == 0;
}
static int skip_lowercase_letters(const char **p) {
int skipped = 0;
for (const char *c = *p; *c >= 'a' && *c <= 'z'; ++c) {
skipped += 1;
}
*p = (*p) + skipped;
return skipped;
}
static int skip_digits(const char **p) {
int skipped = 0;
for (const char *c = *p; *c >= '0' && *c <= '9'; ++c) {
skipped += 1;
}
*p = (*p) + skipped;
return skipped;
}
static int skip_one_char(const char **p, char c) {
if (**p == c) {
*p += 1;
return 1;
}
return 0;
}
static void validate_as_snap_or_component_name(const char *name, int err_code, const char *err_subject,
sc_error **errorp) {
// NOTE: This function should be synchronized with the two other
// implementations: validate_snap_name and snap.ValidateName.
sc_error *err = NULL;
// Ensure that name is not NULL
if (name == NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s cannot be NULL", err_subject);
goto out;
}
if (strlen(name) > SNAP_NAME_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s must be shorter than %d characters", err_subject,
SNAP_NAME_LEN);
goto out;
}
// This is a regexp-free routine hand-codes the following pattern:
//
// "^([a-z0-9]+-?)*[a-z](-?[a-z0-9])*$"
//
// The only motivation for not using regular expressions is so that we
// don't run untrusted input against a potentially complex regular
// expression engine.
const char *p = name;
if (skip_one_char(&p, '-')) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s cannot start with a dash", err_subject);
goto out;
}
bool got_letter = false;
int n = 0, m;
while (*p != '\0') {
if ((m = skip_lowercase_letters(&p)) > 0) {
n += m;
got_letter = true;
continue;
}
if ((m = skip_digits(&p)) > 0) {
n += m;
continue;
}
if (skip_one_char(&p, '-') > 0) {
n++;
if (*p == '\0') {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s cannot end with a dash", err_subject);
goto out;
}
if (skip_one_char(&p, '-') > 0) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s cannot contain two consecutive dashes", err_subject);
goto out;
}
continue;
}
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s must use lower case letters, digits or dashes", err_subject);
goto out;
}
if (!got_letter) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s must contain at least one letter", err_subject);
goto out;
}
if (n < 2) {
err = sc_error_init(SC_SNAP_DOMAIN, err_code, "%s must be longer than 1 character", err_subject);
goto out;
}
out:
sc_error_forward(errorp, err);
}
void sc_instance_name_validate(const char *instance_name, sc_error **errorp) {
// NOTE: This function should be synchronized with the two other
// implementations: validate_instance_name and snap.ValidateInstanceName.
sc_error *err = NULL;
// Ensure that name is not NULL
if (instance_name == NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME, "snap instance name cannot be NULL");
goto out;
}
if (strlen(instance_name) > SNAP_INSTANCE_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
"snap instance name can be at most %d characters long", SNAP_INSTANCE_LEN);
goto out;
}
// instance name length + 1 extra overflow + 1 NULL
char s[SNAP_INSTANCE_LEN + 1 + 1] = {0};
strncpy(s, instance_name, sizeof(s) - 1);
char *t = s;
const char *snap_name = strsep(&t, "_");
const char *instance_key = strsep(&t, "_");
const char *third_separator = strsep(&t, "_");
if (third_separator != NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME,
"snap instance name can contain only one underscore");
goto out;
}
sc_snap_name_validate(snap_name, &err);
if (err != NULL) {
goto out;
}
// When the instance_name is a normal snap name, instance_key will be
// NULL, so only validate instance_key when we found one.
if (instance_key != NULL) {
sc_instance_key_validate(instance_key, &err);
}
out:
sc_error_forward(errorp, err);
}
void sc_instance_key_validate(const char *instance_key, sc_error **errorp) {
// NOTE: see snap.ValidateInstanceName for reference of a valid instance key
// format
sc_error *err = NULL;
// Ensure that name is not NULL
if (instance_key == NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME, "instance key cannot be NULL");
goto out;
}
// This is a regexp-free routine hand-coding the following pattern:
//
// "^[a-z]{1,10}$"
//
// The only motivation for not using regular expressions is so that we don't
// run untrusted input against a potentially complex regular expression
// engine.
int i = 0;
for (i = 0; instance_key[i] != '\0'; i++) {
if (islower(instance_key[i]) || isdigit(instance_key[i])) {
continue;
}
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
"instance key must use lower case letters or digits");
goto out;
}
if (i == 0) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
"instance key must contain at least one letter or digit");
} else if (i > SNAP_INSTANCE_KEY_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
"instance key must be shorter than 10 characters");
}
out:
sc_error_forward(errorp, err);
}
void sc_snap_component_validate(const char *snap_component, const char *snap_instance, sc_error **errorp) {
sc_error *err = NULL;
// ensure that name is not NULL
if (snap_component == NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT, "snap component cannot be NULL");
goto out;
}
const char *pos = strchr(snap_component, '+');
if (pos == NULL) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT, "snap component must contain a +");
goto out;
}
size_t snap_name_len = pos - snap_component;
if (snap_name_len > SNAP_NAME_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT, "snap name must be shorter than 40 characters");
goto out;
}
size_t component_name_len = strlen(pos + 1);
if (component_name_len > SNAP_NAME_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT,
"component name must be shorter than 40 characters");
goto out;
}
char snap_name[SNAP_NAME_LEN + 1] = {0};
strncpy(snap_name, snap_component, snap_name_len);
char component_name[SNAP_NAME_LEN + 1] = {0};
strncpy(component_name, pos + 1, sizeof(component_name) - 1);
validate_as_snap_or_component_name(snap_name, SC_SNAP_INVALID_COMPONENT, "snap name in component", &err);
if (err != NULL) {
goto out;
}
validate_as_snap_or_component_name(component_name, SC_SNAP_INVALID_COMPONENT, "component name", &err);
if (err != NULL) {
goto out;
}
if (snap_instance != NULL) {
char snap_name_in_instance[SNAP_NAME_LEN + 1] = {0};
sc_snap_drop_instance_key(snap_instance, snap_name_in_instance, sizeof snap_name_in_instance);
if (strcmp(snap_name, snap_name_in_instance) != 0) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_COMPONENT,
"snap name in component must match snap name in instance");
goto out;
}
}
out:
sc_error_forward(errorp, err);
}
void sc_snap_name_validate(const char *snap_name, sc_error **errorp) {
validate_as_snap_or_component_name(snap_name, SC_SNAP_INVALID_NAME, "snap name", errorp);
}
void sc_snap_drop_instance_key(const char *instance_name, char *snap_name, size_t snap_name_size) {
sc_snap_split_instance_name(instance_name, snap_name, snap_name_size, NULL, 0);
}
void sc_snap_split_instance_name(const char *instance_name, char *snap_name, size_t snap_name_size, char *instance_key,
size_t instance_key_size) {
sc_string_split(instance_name, '_', snap_name, snap_name_size, instance_key, instance_key_size);
}
void sc_snap_split_snap_component(const char *snap_component, char *snap_name, size_t snap_name_size,
char *component_name, size_t component_name_size) {
sc_string_split(snap_component, '+', snap_name, snap_name_size, component_name, component_name_size);
}
|