From 28dbdbc0f33a0b5fa384607f46cccf07b503c8e3 Mon Sep 17 00:00:00 2001 From: Rafal Krypa Date: Fri, 14 Feb 2014 15:22:53 +0100 Subject: [PATCH] libsmack: enable multi-line support for writing to load2 and change-rule Since Linux 3.12 Smack can handle multiple rules in single write when loading policy. Libsmack will detect this support and group rules into blocks of PAGE_SIZE-1 bytes at most. This results in much smaller number of syscalls and faster loading of large policy. Signed-off-by: Rafal Krypa --- libsmack/libsmack.c | 177 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 135 insertions(+), 42 deletions(-) diff --git a/libsmack/libsmack.c b/libsmack/libsmack.c index b5ec3fd..223954e 100644 --- a/libsmack/libsmack.c +++ b/libsmack/libsmack.c @@ -99,6 +99,7 @@ struct smack_accesses { int has_long; int labels_cnt; int labels_alloc; + int page_size; struct smack_label **labels; struct smack_hash_entry *label_hash; union smack_perm *merge_perms; @@ -119,11 +120,20 @@ struct smack_cipso { struct cipso_mapping *last; }; +struct smack_file_buffer { + int fd; + int pos; + int flush_pos; + char *buf; +}; + static int open_smackfs_file(const char *long_name, const char *short_name, int *use_long); static int accesses_apply(struct smack_accesses *handle, int clear); -static int accesses_print(struct smack_accesses *handle, int clear, - int load_fd, int change_fd, int use_long, int add_lf); +static int accesses_print(struct smack_accesses *handle, + int clear, int use_long, int multiline, + struct smack_file_buffer *load_buffer, + struct smack_file_buffer *change_buffer); static inline ssize_t get_label(char *dest, const char *src, unsigned int *hash); static inline int str_to_access_code(const char *str); static inline void access_code_to_str(unsigned code, char *str); @@ -151,6 +161,8 @@ int smack_accesses_new(struct smack_accesses **accesses) result->label_hash = calloc(DICT_HASH_SIZE, sizeof(struct smack_hash_entry)); if (result->label_hash == NULL) goto err_out; + + result->page_size = sysconf(_SC_PAGESIZE); *accesses = result; return 0; @@ -191,7 +203,17 @@ void smack_accesses_free(struct smack_accesses *handle) int smack_accesses_save(struct smack_accesses *handle, int fd) { - return accesses_print(handle, 0, fd, fd, 1, 1); + struct smack_file_buffer buffer; + int ret; + + buffer.fd = fd; + buffer.buf = malloc(handle->page_size + LOAD_LEN); + if (buffer.buf == NULL) + return -1; + + ret = accesses_print(handle, 0, 1, 1, &buffer, &buffer); + free(buffer.buf); + return ret; } int smack_accesses_apply(struct smack_accesses *handle) @@ -690,41 +712,101 @@ static int open_smackfs_file(const char *long_name, const char *short_name, return fd; } +static inline int check_multiline(int change_fd) +{ + /* This string will be written to kernel Smack "change-rule" interface + * to check if it can handle multiple rules in one write. + * It consists of two rules, separated by '\n': first that does nothing + * and second that has invalid format. If kernel parses only the first + * line (pre-3.12 behavior), it won't see the invalid rule and succeed. + * If it parses both lines, an error will be returned. + */ + static const char test_str[] = "^ ^ - -\n-"; + int ret; + + ret = write(change_fd, test_str, sizeof(test_str) - 1); + if (ret == -1 && errno == EINVAL) + return 1; + return 0; +} + static int accesses_apply(struct smack_accesses *handle, int clear) { int ret; - int load_fd; - int change_fd; int use_long = 1; + int multiline = 0; + struct smack_file_buffer load_buffer = {.fd = -1, .buf = NULL}; + struct smack_file_buffer change_buffer = {.fd = -1, .buf = NULL}; if (init_smackfs_mnt()) return -1; - load_fd = open_smackfs_file("load2", "load", &use_long); - if (load_fd < 0) + load_buffer.fd = open_smackfs_file("load2", "load", &use_long); + if (load_buffer.fd < 0) return -1; - - change_fd = openat(smackfs_mnt_dirfd, "change-rule", O_WRONLY); - /* Try to continue if the file doesn't exist, we might not need it. */ - if (change_fd < 0 && errno != ENOENT) { - ret = -1; + load_buffer.buf = malloc(handle->page_size + LOAD_LEN); + if (load_buffer.buf == NULL) goto err_out; + + change_buffer.fd = openat(smackfs_mnt_dirfd, "change-rule", O_WRONLY); + if (change_buffer.fd >= 0) { + change_buffer.buf = malloc(handle->page_size + LOAD_LEN); + if (change_buffer.buf == NULL) + goto err_out; + + multiline = check_multiline(change_buffer.fd); + } else { + /* Try to continue if "change-rule" doesn't exist, we might + * not need it. */ + if (errno != ENOENT) + goto err_out; } - ret = accesses_print(handle, clear, load_fd, change_fd, use_long, 0); + ret = accesses_print(handle, clear, use_long, multiline, + &load_buffer, &change_buffer); + goto out; err_out: - if (load_fd >= 0) - close(load_fd); - if (change_fd >= 0) - close(change_fd); + ret = -1; +out: + if (load_buffer.fd >= 0) + close(load_buffer.fd); + if (change_buffer.fd >= 0) + close(change_buffer.fd); + free(load_buffer.buf); + free(change_buffer.buf); return ret; } +static int buffer_flush(struct smack_file_buffer *buf) +{ + int pos; + int ret; + + /* Write buffered bytes to kernel, up to flush_pos */ + for (pos = 0; pos < buf->flush_pos; ) { + ret = write(buf->fd, buf->buf + pos, buf->flush_pos - pos); + if (ret == -1) { + if (errno != EINTR) + return -1; + } else + pos += ret; + } + + /* Move remaining, not flushed bytes to the buffer start */ + memcpy(buf->buf, buf->buf + pos, buf->pos - pos); + buf->pos -= pos; + buf->flush_pos = 0; + + return 0; +} + static int accesses_print(struct smack_accesses *handle, int clear, - int load_fd, int change_fd, int use_long, int add_lf) + int use_long, int multiline, + struct smack_file_buffer *load_buffer, + struct smack_file_buffer *change_buffer) { - char buf[LOAD_LEN + 1]; + struct smack_file_buffer *buffer; char allow_str[ACC_LEN + 1]; char deny_str[ACC_LEN + 1]; struct smack_label *subject_label; @@ -732,16 +814,14 @@ static int accesses_print(struct smack_accesses *handle, int clear, struct smack_rule *rule; union smack_perm *perm; int merge_cnt; - int ret; - int fd; - int i; int x; int y; - int cnt; if (!use_long && handle->has_long) return -1; + load_buffer->pos = 0; + change_buffer->pos = 0; bzero(handle->merge_perms, handle->labels_cnt * sizeof(union smack_perm)); for (x = 0; x < handle->labels_cnt; ++x) { subject_label = handle->labels[x]; @@ -770,45 +850,58 @@ static int accesses_print(struct smack_accesses *handle, int clear, if ((perm->allow_code | perm->deny_code) != ACCESS_TYPE_ALL) { /* Fail immediately without doing any further processing if modify rules are not supported. */ - if (change_fd < 0) + if (change_buffer->fd < 0) return -1; - fd = change_fd; + buffer = change_buffer; + buffer->flush_pos = buffer->pos; access_code_to_str(perm->deny_code, deny_str); - cnt = snprintf(buf, LOAD_LEN + 1, KERNEL_MODIFY_FORMAT, + buffer->pos += sprintf(buffer->buf + buffer->pos, + KERNEL_MODIFY_FORMAT, subject_label->label, object_label->label, allow_str, deny_str); } else { - fd = load_fd; + buffer = load_buffer; + buffer->flush_pos = buffer->pos; if (use_long) - cnt = snprintf(buf, LOAD_LEN + 1, KERNEL_LONG_FORMAT, + buffer->pos += sprintf(buffer->buf + buffer->pos, + KERNEL_LONG_FORMAT, subject_label->label, object_label->label, allow_str); else - cnt = snprintf(buf, LOAD_LEN + 1, KERNEL_SHORT_FORMAT, + buffer->pos += sprintf(buffer->buf + buffer->pos, + KERNEL_SHORT_FORMAT, subject_label->label, object_label->label, allow_str); } perm->allow_deny_code = 0; - if (cnt < 0) - return -1; - - if (add_lf) - buf[cnt++] = '\n'; - - for (i = 0; i < cnt; ) { - ret = write(fd, buf + i, cnt - i); - if (ret == -1) { - if (errno == EINTR) - continue; + if (multiline) { + buffer->buf[buffer->pos++] = '\n'; + if (buffer->pos >= handle->page_size) + if (buffer_flush(buffer)) + return -1; + } else { + /* When no multi-line is supported, just flush + * the rule that was just generated */ + buffer->flush_pos = buffer->pos; + if (buffer_flush(buffer)) return -1; - } - i += ret; } } } + if (load_buffer->pos > 0) { + load_buffer->flush_pos = load_buffer->pos; + if (buffer_flush(load_buffer)) + return -1; + } + if (change_buffer->pos > 0) { + change_buffer->flush_pos = change_buffer->pos; + if (buffer_flush(change_buffer)) + return -1; + } + return 0; } -- 2.7.4