/* * Copyright (c) 2014 * Canonical, Ltd. (All rights reserved) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License 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, contact Novell, Inc. or Canonical * Ltd. */ #include #include #include #include #include #include #include #include #include #include "private.h" #define DEFAULT_APPARMORFS "/sys/kernel/security/apparmor" struct aa_kernel_interface { unsigned int ref_count; bool supports_setload; int dirfd; }; /** * find_iface_dir - find where the apparmor interface is located * @dir - RETURNs: stored location of interface director * * Returns: 0 on success, -1 with errno set if there is an error */ static int find_iface_dir(char **dir) { if (aa_find_mountpoint(dir) == -1) { struct stat buf; if (stat(DEFAULT_APPARMORFS, &buf) == -1) { return -1; } else { *dir = strdup(DEFAULT_APPARMORFS); if (*dir == NULL) return -1; } } return 0; } /* bleah the kernel should just loop and do multiple load, but to support * older systems we need to do this */ #define PROFILE_HEADER_SIZE static char header_version[] = "\x04\x08\x00version"; static const char *next_profile_buffer(const char *buffer, int size) { const char *b = buffer; for (; size - sizeof(header_version); b++, size--) { if (memcmp(b, header_version, sizeof(header_version)) == 0) { return b; } } return NULL; } static int write_buffer(int fd, const char *buffer, int size) { int wsize = write(fd, buffer, size); if (wsize < 0) { return -1; } else if (wsize < size) { errno = EPROTO; return -1; } return 0; } /** * write_policy_buffer - load compiled policy into the kernel * @fd: kernel iterface to write to * @atomic: whether to load all policy in buffer atomically (true) * @buffer: buffer of policy to load * @size: the size of the data in the buffer * * Returns: 0 if the buffer loaded correctly * -1 if the load failed with errno set to the error * * @atomic should only be set to true if the kernel supports atomic profile * set loads, otherwise only the 1st profile in the buffer will be loaded * (older kernels only support loading one profile at a time). */ static int write_policy_buffer(int fd, int atomic, const char *buffer, size_t size) { size_t bsize; int rc; if (atomic) { rc = write_buffer(fd, buffer, size); } else { const char *b, *next; rc = 0; /* in case there are no profiles */ for (b = buffer; b; b = next, size -= bsize) { next = next_profile_buffer(b + sizeof(header_version), size); if (next) bsize = next - b; else bsize = size; if (write_buffer(fd, b, bsize) == -1) return -1; } } if (rc) return -1; return 0; } #define AA_IFACE_FILE_LOAD ".load" #define AA_IFACE_FILE_REMOVE ".remove" #define AA_IFACE_FILE_REPLACE ".replace" static int write_policy_buffer_to_iface(aa_kernel_interface *kernel_interface, const char *iface_file, const char *buffer, size_t size) { autoclose int fd = -1; fd = openat(kernel_interface->dirfd, iface_file, O_WRONLY | O_CLOEXEC); if (fd == -1) return -1; return write_policy_buffer(fd, kernel_interface->supports_setload, buffer, size); } static int write_policy_fd_to_iface(aa_kernel_interface *kernel_interface, const char *iface_file, int fd) { autofree char *buffer = NULL; int size = 0, asize = 0, rsize; int chunksize = 1 << 14; do { if (asize - size == 0) { char *tmp = realloc(buffer, chunksize); asize = chunksize; chunksize <<= 1; if (!tmp) { errno = ENOMEM; return -1; } buffer = tmp; } rsize = read(fd, buffer + size, asize - size); if (rsize) size += rsize; } while (rsize > 0); if (rsize == -1) return -1; return write_policy_buffer_to_iface(kernel_interface, iface_file, buffer, size); } static int write_policy_file_to_iface(aa_kernel_interface *kernel_interface, const char *iface_file, int dirfd, const char *path) { autoclose int fd; fd = openat(dirfd, path, O_RDONLY); if (fd == -1) return -1; return write_policy_fd_to_iface(kernel_interface, iface_file, fd); } /** * aa_kernel_interface_new - create a new aa_kernel_interface object from an optional path * @kernel_interface: will point to the address of an allocated and initialized * aa_kernel_interface object upon success * @kernel_features: features representing the currently running kernel (can be * NULL and the features of the currently running kernel will * be used) * @apparmorfs: path to the apparmor directory of the mounted securityfs (can * be NULL and the path will be auto discovered) * * Returns: 0 on success, -1 on error with errnot set and *@kernel_interface * pointing to NULL */ int aa_kernel_interface_new(aa_kernel_interface **kernel_interface, aa_features *kernel_features, const char *apparmorfs) { aa_kernel_interface *ki; autofree char *alloced_apparmorfs = NULL; char set_load[] = "policy/set_load"; *kernel_interface = NULL; ki = calloc(1, sizeof(*ki)); if (!ki) { errno = ENOMEM; return -1; } aa_kernel_interface_ref(ki); ki->dirfd = -1; if (kernel_features) { aa_features_ref(kernel_features); } else if (aa_features_new_from_kernel(&kernel_features) == -1) { aa_kernel_interface_unref(ki); return -1; } ki->supports_setload = aa_features_supports(kernel_features, set_load); aa_features_unref(kernel_features); if (!apparmorfs) { if (find_iface_dir(&alloced_apparmorfs) == -1) { alloced_apparmorfs = NULL; aa_kernel_interface_unref(ki); return -1; } /* alloced_apparmorfs will be autofree'ed */ apparmorfs = alloced_apparmorfs; } ki->dirfd = open(apparmorfs, O_RDONLY | O_CLOEXEC | O_DIRECTORY); if (ki->dirfd < 0) { aa_kernel_interface_unref(ki); return -1; } *kernel_interface = ki; return 0; } /** * aa_kernel_interface_ref - increments the ref count of an aa_kernel_interface object * @kernel_interface: the kernel_interface * * Returns: the kernel_interface */ aa_kernel_interface *aa_kernel_interface_ref(aa_kernel_interface *kernel_interface) { atomic_inc(&kernel_interface->ref_count); return kernel_interface; } /** * aa_kernel_interface_unref - decrements the ref count and frees the aa_kernel_interface object when 0 * @kernel_interface: the kernel_interface (can be NULL) */ void aa_kernel_interface_unref(aa_kernel_interface *kernel_interface) { int save = errno; if (kernel_interface && atomic_dec_and_test(&kernel_interface->ref_count)) { if (kernel_interface->dirfd >= 0) close(kernel_interface->dirfd); free(kernel_interface); } errno = save; } /** * aa_kernel_interface_load_policy - load a policy from a buffer into the kernel * @kernel_interface: valid aa_kernel_interface * @buffer: a buffer containing a policy * @size: the size of the buffer * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_load_policy(aa_kernel_interface *kernel_interface, const char *buffer, size_t size) { return write_policy_buffer_to_iface(kernel_interface, AA_IFACE_FILE_LOAD, buffer, size); } /** * aa_kernel_interface_load_policy_from_file - load a policy from a file into the kernel * @kernel_interface: valid aa_kernel_interface * @path: path to a policy binary * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_load_policy_from_file(aa_kernel_interface *kernel_interface, int dirfd, const char *path) { return write_policy_file_to_iface(kernel_interface, AA_IFACE_FILE_LOAD, dirfd, path); } /** * aa_kernel_interface_load_policy_from_fd - load a policy from a file descriptor into the kernel * @kernel_interface: valid aa_kernel_interface * @fd: a pre-opened, readable file descriptor at the correct offset * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_load_policy_from_fd(aa_kernel_interface *kernel_interface, int fd) { return write_policy_fd_to_iface(kernel_interface, AA_IFACE_FILE_LOAD, fd); } /** * aa_kernel_interface_replace_policy - replace a policy in the kernel with a policy from a buffer * @kernel_interface: valid aa_kernel_interface * @buffer: a buffer containing a policy * @size: the size of the buffer * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_replace_policy(aa_kernel_interface *kernel_interface, const char *buffer, size_t size) { return write_policy_buffer_to_iface(kernel_interface, AA_IFACE_FILE_REPLACE, buffer, size); } /** * aa_kernel_interface_replace_policy_from_file - replace a policy in the kernel with a policy from a file * @kernel_interface: valid aa_kernel_interface * @path: path to a policy binary * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_replace_policy_from_file(aa_kernel_interface *kernel_interface, int dirfd, const char *path) { return write_policy_file_to_iface(kernel_interface, AA_IFACE_FILE_REPLACE, dirfd, path); } /** * aa_kernel_interface_replace_policy_from_fd - replace a policy in the kernel with a policy from a file descriptor * @kernel_interface: valid aa_kernel_interface * @fd: a pre-opened, readable file descriptor at the correct offset * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_replace_policy_from_fd(aa_kernel_interface *kernel_interface, int fd) { return write_policy_fd_to_iface(kernel_interface, AA_IFACE_FILE_REPLACE, fd); } /** * aa_kernel_interface_remove_policy - remove a policy from the kernel * @kernel_interface: valid aa_kernel_interface * @fqname: nul-terminated fully qualified name of the policy to remove * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_remove_policy(aa_kernel_interface *kernel_interface, const char *fqname) { return write_policy_buffer_to_iface(kernel_interface, AA_IFACE_FILE_REMOVE, fqname, strlen(fqname) + 1); } /** * aa_kernel_interface_write_policy - write a policy to a file descriptor * @fd: a pre-opened, writeable file descriptor at the correct offset * @buffer: a buffer containing a policy * @size: the size of the buffer * * Returns: 0 on success, -1 on error with errno set */ int aa_kernel_interface_write_policy(int fd, const char *buffer, size_t size) { return write_policy_buffer(fd, 1, buffer, size); }