#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/xattr.h>
#include <unistd.h>
- #include <limits.h>
#include <sys/xattr.h>
#define SELF_LABEL_FILE "/proc/self/attr/current"
struct cipso_mapping *last;
};
+typedef int (*getxattr_func)(void*, const char*, void*, size_t);
+typedef int (*setxattr_func)(const void*, const char*, const void*, size_t, int);
+typedef int (*removexattr_func)(void*, const char*);
+
+static inline char* get_xattr_name(enum smack_label_type type);
++
+ 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 inline ssize_t get_label(char *dest, const char *src);
+ 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);
+ static struct smack_label *label_add(struct smack_accesses *handle, const char *src);
int smack_accesses_new(struct smack_accesses **accesses)
{
return (ret < 0) ? -1 : 0;
}
- static int accesses_apply(struct smack_accesses *handle, int clear)
+static int internal_getlabel(void* file, char** label,
+ enum smack_label_type type,
+ getxattr_func getfunc)
+{
+ char* xattr_name = get_xattr_name(type);
+ char value[SMACK_LABEL_LEN + 1];
+ int ret;
+
+ ret = getfunc(file, xattr_name, value, SMACK_LABEL_LEN + 1);
+ if (ret == -1) {
+ if (errno == ENODATA) {
+ *label = NULL;
+ return 0;
+ }
+ return -1;
+ }
+
+ value[ret] = '\0';
+ *label = calloc(ret + 1, 1);
+ if (*label == NULL)
+ return -1;
+ strncpy(*label, value, ret);
+ return 0;
+}
+
+static int internal_setlabel(void* file, const char* label,
+ enum smack_label_type type,
+ setxattr_func setfunc, removexattr_func removefunc)
+{
+ char* xattr_name = get_xattr_name(type);
+
+ /* Check validity of labels for LABEL_TRANSMUTE */
+ if (type == SMACK_LABEL_TRANSMUTE && label != NULL) {
+ if (!strcmp(label, "0"))
+ label = NULL;
+ else if (!strcmp(label, "1"))
+ label = "TRUE";
+ else
+ return -1;
+ }
+
+ if (label == NULL || label[0] == '\0') {
+ return removefunc(file, xattr_name);
+ } else {
+ int len = strnlen(label, SMACK_LABEL_LEN + 1);
+ if (len > SMACK_LABEL_LEN)
+ return -1;
+ return setfunc(file, xattr_name, label, len, 0);
+ }
+}
+
+int smack_getlabel(const char *path, char** label,
+ enum smack_label_type type)
+{
+ return internal_getlabel((void*) path, label, type,
+ (getxattr_func) getxattr);
+}
+
+int smack_lgetlabel(const char *path, char** label,
+ enum smack_label_type type)
+{
+ return internal_getlabel((void*) path, label, type,
+ (getxattr_func) lgetxattr);
+}
+
+int smack_fgetlabel(int fd, char** label,
+ enum smack_label_type type)
+{
+ return internal_getlabel((void*) ((unsigned long) fd), label, type,
+ (getxattr_func) fgetxattr);
+}
+
+int smack_setlabel(const char *path, const char* label,
+ enum smack_label_type type)
+{
+ return internal_setlabel((void*) path, label, type,
+ (setxattr_func) setxattr, (removexattr_func) removexattr);
+}
+
+int smack_lsetlabel(const char *path, const char* label,
+ enum smack_label_type type)
+{
+ return internal_setlabel((void*) path, label, type,
+ (setxattr_func) lsetxattr, (removexattr_func) lremovexattr);
+}
+
+int smack_fsetlabel(int fd, const char* label,
+ enum smack_label_type type)
+{
+ return internal_setlabel((void*) ((unsigned long) fd), label, type,
+ (setxattr_func) fsetxattr, (removexattr_func) fremovexattr);
+}
+
+ static int open_smackfs_file(const char *long_name, const char *short_name,
+ int *use_long)
{
- char buf[LOAD_LEN + 1];
- char allow_str[ACC_LEN + 1];
- char deny_str[ACC_LEN + 1];
- struct smack_rule *rule;
- int ret;
int fd;
- int load_fd;
- int change_fd;
- int load2 = 1;
- char path[PATH_MAX];
- if (!smackfs_mnt)
- return -1;
-
- snprintf(path, sizeof path, "%s/load2", smackfs_mnt);
- load_fd = open(path, O_WRONLY);
- if (load_fd < 0) {
+ fd = openat(smackfs_mnt_dirfd, long_name, O_WRONLY);
+ if (fd < 0) {
if (errno != ENOENT)
return -1;
- /* fallback */
- snprintf(path, sizeof path, "%s/load", smackfs_mnt);
- load_fd = open(path, O_WRONLY);
- /* Try to continue if the file doesn't exist, we might not need it. */
- if (load_fd < 0 && errno != ENOENT)
+
+ fd = openat(smackfs_mnt_dirfd, short_name, O_WRONLY);
+ if (fd < 0)
return -1;
- load2 = 0;
+
+ *use_long = 0;
+ return fd;
}
- snprintf(path, sizeof path, "%s/change-rule", smackfs_mnt);
- change_fd = open(path, O_WRONLY);
- /* Try to continue if the file doesn't exist, we might not need it. */
- if (change_fd < 0 && errno != ENOENT) {
- ret = -1;
+ *use_long = 1;
+ 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 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_buffer.fd = open_smackfs_file("load2", "load", &use_long);
+ if (load_buffer.fd < 0)
+ return -1;
+ load_buffer.buf = malloc(handle->page_size + LOAD_LEN);
+ if (load_buffer.buf == NULL)
goto err_out;
- }
- for (rule = handle->first; rule != NULL; rule = rule->next) {
- /* Fail immediately without doing any further processing
- if modify rules are not supported. */
- if (rule->deny_code >= 0 && change_fd < 0) {
- ret = -1;
+ 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;
- }
- access_code_to_str(clear ? 0 : rule->allow_code, allow_str);
+ 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;
+ }
- if (rule->deny_code != -1 && !clear) {
- access_code_to_str(rule->deny_code, deny_str);
+ ret = accesses_print(handle, clear, use_long, multiline,
+ &load_buffer, &change_buffer);
+ goto out;
- fd = change_fd;
- ret = snprintf(buf, LOAD_LEN + 1, KERNEL_MODIFY_FORMAT,
- rule->subject, rule->object,
- allow_str,
- deny_str);
- } else {
- fd = load_fd;
- if (load2)
- ret = snprintf(buf, LOAD_LEN + 1, KERNEL_LONG_FORMAT,
- rule->subject, rule->object,
- allow_str);
- else {
- if (rule->subject_len > SHORT_LABEL_LEN ||
- rule->object_len > SHORT_LABEL_LEN) {
- ret = -1;
- goto err_out;
- }
-
- ret = snprintf(buf, LOAD_LEN + 1, KERNEL_SHORT_FORMAT,
- rule->subject, rule->object,
- allow_str);
+ err_out:
+ 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 inline void rule_print_long(char *buf, int *pos,
+ struct smack_label *subject_label, struct smack_label *object_label,
+ const char *allow_str, const char *deny_str)
+ {
+ memcpy(buf + *pos, subject_label->label, subject_label->len);
+ *pos += subject_label->len;
+ buf[(*pos)++] = ' ';
+ memcpy(buf + *pos, object_label->label, object_label->len);
+ *pos += object_label->len;
+ buf[(*pos)++] = ' ';
+ memcpy(buf + *pos, allow_str, ACC_LEN);
+ *pos += ACC_LEN;
+ if (deny_str != NULL) {
+ buf[(*pos)++] = ' ';
+ memcpy(buf + *pos, deny_str, ACC_LEN);
+ *pos += ACC_LEN;
+ }
+ }
+
+ 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)
+ {
+ struct smack_file_buffer *buffer;
+ char allow_str[ACC_LEN + 1];
+ char deny_str[ACC_LEN + 1];
+ struct smack_label *subject_label;
+ struct smack_label *object_label;
+ struct smack_rule *rule;
+ union smack_perm *perm;
+ int merge_cnt;
+ int x;
+ int y;
+
+ 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];
+ merge_cnt = 0;
+ for (rule = subject_label->first_rule; rule != NULL; rule = rule->next_rule) {
+ perm = &(handle->merge_perms[rule->object_id]);
+ if (perm->allow_deny_code == 0)
+ handle->merge_object_ids[merge_cnt++] = rule->object_id;
+
+ if (clear) {
+ perm->allow_code = 0;
+ perm->deny_code = ACCESS_TYPE_ALL;
+ } else {
+ perm->allow_code |= rule->perm.allow_code;
+ perm->allow_code &= ~rule->perm.deny_code;
+ perm->deny_code &= ~rule->perm.allow_code;
+ perm->deny_code |= rule->perm.deny_code;
}
}
str[6] = '\0';
}
+static inline char* get_xattr_name(enum smack_label_type type)
+{
+ switch (type) {
+ case SMACK_LABEL_ACCESS:
+ return "security.SMACK64";
+ case SMACK_LABEL_EXEC:
+ return "security.SMACK64EXEC";
+ case SMACK_LABEL_MMAP:
+ return "security.SMACK64MMAP";
+ case SMACK_LABEL_TRANSMUTE:
+ return "security.SMACK64TRANSMUTE";
+ case SMACK_LABEL_IPIN:
+ return "security.SMACK64IPIN";
+ case SMACK_LABEL_IPOUT:
+ return "security.SMACK64IPOUT";
+ default:
+ /* Should not reach this point */
+ return NULL;
+ }
++}
++
+ static inline struct smack_label *
+ is_label_known(struct smack_accesses *handle, const char *label, int hash)
+ {
+ struct smack_label *lab = handle->label_hash[hash].first;
+ while (lab != NULL && strcmp(label, lab->label) != 0)
+ lab = lab->next_label;
+ return lab;
+ }
+
+ static inline int accesses_resize(struct smack_accesses *handle)
+ {
+ struct smack_label **labels;
+ union smack_perm *merge_perms;
+ int *merge_object_ids;
+ int alloc = handle->labels_alloc << 1;
+
+ labels = realloc(handle->labels, alloc * sizeof(struct smack_label *));
+ if (labels == NULL)
+ return -1;
+ handle->labels = labels;
+
+ merge_perms = realloc(handle->merge_perms, alloc * sizeof(union smack_perm));
+ if (merge_perms == NULL)
+ return -1;
+ handle->merge_perms = merge_perms;
+
+ merge_object_ids = realloc(handle->merge_object_ids, alloc * sizeof(int));
+ if (merge_object_ids == NULL)
+ return -1;
+ handle->merge_object_ids = merge_object_ids;
+
+ handle->labels_alloc = alloc;
+ return 0;
+ }
+
+ static struct smack_label *label_add(struct smack_accesses *handle, const char *label)
+ {
+ struct smack_hash_entry *hash_entry;
+ unsigned int hash_value = 0;
+ struct smack_label *new_label;
+ int len;
+
+ len = get_label(NULL, label, &hash_value);
+ if (len == -1)
+ return NULL;
+
+ new_label = is_label_known(handle, label, hash_value);
+ if (new_label == NULL) {/*no entry added yet*/
+ if (handle->labels_cnt == handle->labels_alloc)
+ if (accesses_resize(handle))
+ return NULL;
+
+ new_label = malloc(sizeof(struct smack_label));
+ if (new_label == NULL)
+ return NULL;
+ new_label->label = malloc(len + 1);
+ if (new_label->label == NULL)
+ return NULL;
+
+ memcpy(new_label->label, label, len + 1);
+ new_label->id = handle->labels_cnt;
+ new_label->len = len;
+ new_label->first_rule = NULL;
+ new_label->last_rule = NULL;
+ new_label->next_label = NULL;
+ hash_entry = &(handle->label_hash[hash_value]);
+ if (hash_entry->first == NULL) {
+ hash_entry->first = new_label;
+ hash_entry->last = new_label;
+ } else {
+ hash_entry->last->next_label = new_label;
+ hash_entry->last = new_label;
+ }
+ handle->labels[handle->labels_cnt++] = new_label;
+ }
+ return new_label;
}