File: snap.c

package info (click to toggle)
snapd 2.72-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 80,412 kB
  • sloc: sh: 16,506; ansic: 16,211; python: 11,213; makefile: 1,919; exp: 190; awk: 58; xml: 22
file content (357 lines) | stat: -rw-r--r-- 12,979 bytes parent folder | download | duplicates (2)
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);
}