3
* Canonical, Ltd. (All rights reserved)
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of version 2 of the GNU General Public
7
* License published by the Free Software Foundation.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, contact Novell, Inc. or Canonical
27
#include <sys/types.h>
30
#include <sys/apparmor.h>
34
#define FEATURES_FILE "/sys/kernel/security/apparmor/features"
36
#define STRING_SIZE 8192
39
unsigned int ref_count;
40
char string[STRING_SIZE];
43
struct features_struct {
54
static int features_snprintf(struct features_struct *fst, const char *fmt, ...)
57
int i, remaining = fst->size - (fst->pos - fst->buffer);
61
PERROR("Invalid features buffer offset\n");
66
i = vsnprintf(fst->pos, remaining, fmt, args);
71
PERROR("Failed to write to features buffer\n");
73
} else if (i >= remaining) {
75
PERROR("Feature buffer full.");
83
/* load_features_file - opens and reads a file into @buffer and then NUL-terminates @buffer
84
* @dirfd: a directory file descriptory or AT_FDCWD (see openat(2))
85
* @path: name of the file
86
* @buffer: the buffer to read the features file into (will be NUL-terminated on success)
87
* @size: the size of @buffer
89
* Returns: The number of bytes copied into @buffer on success (not counting
90
* the NUL-terminator), else -1 and errno is set. Note that @size must be
91
* larger than the size of the file or -1 will be returned with errno set to
92
* ENOBUFS indicating that @buffer was not large enough to contain all of the
95
static int load_features_file(int dirfd, const char *path,
96
char *buffer, size_t size)
98
autoclose int file = -1;
102
file = openat(dirfd, path, O_RDONLY);
104
PDEBUG("Could not open '%s'\n", path);
107
PDEBUG("Opened features \"%s\"\n", path);
114
/* Save room for a NUL-terminator at the end of @buffer */
118
len = read(file, pos, size);
123
} while (len > 0 && size);
126
* Possible error conditions:
127
* - len == -1: read failed and errno is already set so return -1
128
* - len > 0: read() never returned 0 (EOF) meaning that @buffer was
129
* too small to contain all of the file contents so set
130
* errno to ENOBUFS and return -1
136
PDEBUG("Error reading features file '%s': %m\n", path);
140
/* NUL-terminate @buffer */
146
static int features_dir_cb(int dirfd, const char *name, struct stat *st,
149
struct features_struct *fst = (struct features_struct *) data;
151
/* skip dot files and files with no name */
152
if (*name == '.' || !strlen(name))
155
if (features_snprintf(fst, "%s {", name) == -1)
158
if (S_ISREG(st->st_mode)) {
160
int remaining = fst->size - (fst->pos - fst->buffer);
162
len = load_features_file(dirfd, name, fst->pos, remaining);
167
} else if (S_ISDIR(st->st_mode)) {
168
if (_aa_dirat_for_each(dirfd, name, fst, features_dir_cb))
172
if (features_snprintf(fst, "}\n") == -1)
178
static int load_features_dir(int dirfd, const char *path,
179
char *buffer, int size)
181
struct features_struct fst = { buffer, size, buffer };
183
if (_aa_dirat_for_each(dirfd, path, &fst, features_dir_cb)) {
184
PDEBUG("Failed evaluating %s\n", path);
191
static bool islbrace(int c)
196
static bool isrbrace(int c)
201
static bool isnul(int c)
206
static bool isbrace(int c)
208
return islbrace(c) || isrbrace(c);
211
static bool isbrace_or_nul(int c)
213
return isbrace(c) || isnul(c);
216
static bool isbrace_space_or_nul(int c)
218
return isbrace(c) || isspace(c) || isnul(c);
221
static size_t tokenize_path_components(const char *str,
222
struct component *components,
223
size_t max_components)
227
memset(components, 0, sizeof(*components) * max_components);
232
while (*str && i < max_components) {
233
const char *fwdslash = strchrnul(str, '/');
235
/* Save the token if it is not "/" */
236
if (fwdslash != str) {
237
components[i].str = str;
238
components[i].len = fwdslash - str;
242
if (isnul(*fwdslash))
252
* walk_one - walk one component of a features string
253
* @str: a pointer to the current position in a features string
254
* @component: the component to walk
255
* @is_top_level: true if component is a top-level component
257
* Imagine a features string such as:
259
* "feat1 { subfeat1.1 subfeat1.2 } feat2 { subfeat2.1 { subfeat2.1.1 } }"
261
* You want to know if "feat2/subfeat2.1/subfeat2.8" is valid. It will take 3
262
* invocations of this function to determine if that string is valid. On the
263
* first call, *@str will point to the beginning of the features string,
264
* component->str will be "feat2", and is_top_level will be true since feat2 is
265
* a top level feature. The function will return true and *@str will now point
266
* at the the left brace after "feat2" in the features string. You can call
267
* this function again with component->str being equal to "subfeat2.1" and it
268
* will return true and *@str will now point at the left brace after
269
* "subfeat2.1" in the features string. A third call to the function, with
270
* component->str equal to "subfeat2.8", will return false and *@str will not
273
* Returns true if the walk was successful and false otherwise. If the walk was
274
* successful, *@str will point to the first encountered brace after the walk.
275
* If the walk was unsuccessful, *@str is not updated.
277
static bool walk_one(const char **str, const struct component *component,
281
uint32_t brace_count = 0;
284
/* NULL pointers and empty strings are not accepted */
285
if (!str || !*str || !component || !component->str || !component->len)
291
* If @component is not top-level, the first character in the string to
302
* This loop tries to find the @component in *@str. When this loops
303
* completes, cur will either point one character past the end of the
304
* matched @component or to the NUL terminator of *@str.
306
while(!isnul(*cur) && i < component->len) {
307
if (!isascii(*cur)) {
308
/* Only ASCII is expected */
310
} else if (islbrace(*cur)) {
311
/* There's a limit to the number of left braces */
312
if (brace_count == UINT32_MAX)
316
} else if (isrbrace(*cur)) {
317
/* Check for unexpected right braces */
318
if (brace_count == 0)
325
* Move to the next character in @component if we're not inside
326
* of braces and we have a character match
328
if (brace_count == 0 && *cur == component->str[i])
336
/* Return false if a full match was not found */
337
if (i != component->len) {
339
} else if (!isbrace_space_or_nul(*cur))
343
* This loop eats up valid (ASCII) characters until a brace or NUL
344
* character is found so that *@str is properly set to call back into
347
while (!isbrace_or_nul(*cur)) {
359
* aa_features_new - create a new aa_features object based on a path
360
* @features: will point to the address of an allocated and initialized
361
* aa_features object upon success
362
* @dirfd: directory file descriptor or AT_FDCWD (see openat(2))
363
* @path: path to a features file or directory
365
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
368
int aa_features_new(aa_features **features, int dirfd, const char *path)
370
struct stat stat_file;
376
if (fstatat(dirfd, path, &stat_file, 0) == -1)
379
f = calloc(1, sizeof(*f));
386
retval = S_ISDIR(stat_file.st_mode) ?
387
load_features_dir(dirfd, path, f->string, STRING_SIZE) :
388
load_features_file(dirfd, path, f->string, STRING_SIZE);
392
aa_features_unref(f);
403
* aa_features_new_from_string - create a new aa_features object based on a string
404
* @features: will point to the address of an allocated and initialized
405
* aa_features object upon success
406
* @string: a NUL-terminated string representation of features
407
* @size: the size of @string, not counting the NUL-terminator
409
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
412
int aa_features_new_from_string(aa_features **features,
413
const char *string, size_t size)
419
/* Require size to be less than STRING_SIZE so there's room for a NUL */
420
if (size >= STRING_SIZE)
423
f = calloc(1, sizeof(*f));
430
memcpy(f->string, string, size);
431
f->string[size] = '\0';
438
* aa_features_new_from_kernel - create a new aa_features object based on the current kernel
439
* @features: will point to the address of an allocated and initialized
440
* aa_features object upon success
442
* Returns: 0 on success, -1 on error with errno set and *@features pointing to
445
int aa_features_new_from_kernel(aa_features **features)
447
return aa_features_new(features, -1, FEATURES_FILE);
451
* aa_features_ref - increments the ref count of an aa_features object
452
* @features: the features
454
* Returns: the features
456
aa_features *aa_features_ref(aa_features *features)
458
atomic_inc(&features->ref_count);
463
* aa_features_unref - decrements the ref count and frees the aa_features object when 0
464
* @features: the features (can be NULL)
466
void aa_features_unref(aa_features *features)
468
if (features && atomic_dec_and_test(&features->ref_count))
473
* aa_features_write_to_file - write a string representation of an aa_features object to a file
474
* @features: the features
475
* @dirfd: directory file descriptor or AT_FDCWD (see openat(2))
476
* @path: the path to write to
478
* Returns: 0 on success, -1 on error with errno set
480
int aa_features_write_to_file(aa_features *features,
481
int dirfd, const char *path)
483
autoclose int fd = -1;
488
fd = openat(dirfd, path,
489
O_WRONLY | O_CREAT | O_TRUNC | O_SYNC | O_CLOEXEC,
490
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
494
string = features->string;
495
size = strlen(string);
497
retval = write(fd, string, size);
509
* aa_features_is_equal - equality test for two aa_features objects
510
* @features1: the first features (can be NULL)
511
* @features2: the second features (can be NULL)
513
* Returns: true if they're equal, false if they're not or either are NULL
515
bool aa_features_is_equal(aa_features *features1, aa_features *features2)
517
return features1 && features2 &&
518
strcmp(features1->string, features2->string) == 0;
522
* aa_features_supports - provides aa_features object support status
523
* @features: the features
524
* @str: the string representation of a feature to check
526
* Example @str values are "dbus/mask/send", "caps/mask/audit_read", and
527
* "policy/versions/v7".
529
* Returns: a bool specifying the support status of @str feature
531
bool aa_features_supports(aa_features *features, const char *str)
533
const char *features_string = features->string;
534
struct component components[32];
535
size_t i, num_components;
537
/* Empty strings are not accepted. Neither are leading '/' chars. */
538
if (!str || str[0] == '/')
542
* Break @str into an array of components. For example,
543
* "mount/mask/mount" would turn into { "mount", 5 } as the first
544
* component, { "mask", 4 } as the second, and { "mount", 5 } as the
547
num_components = tokenize_path_components(str, components,
548
sizeof(components) / sizeof(*components));
550
/* At least one valid token is required */
554
/* Ensure that all components are valid and found */
555
for (i = 0; i < num_components; i++) {
556
if (!walk_one(&features_string, &components[i], i == 0))