Separate properties by selabel

The purpose of this change is to add read access control to the property
space.

In the current design, a process either has access to the single
/dev/__properties__ file and therefore all properties that it contains
or it has access to no properties.  This change separates properties
into multiple property files based on their selabel, which allows
creation of sepolicies that allow read access of only specific sets of
properties to specific domains.

Bug 21852512

Change-Id: Ice265db79201ca811c6b6cf6d851703f53224f03
This commit is contained in:
Tom Cherry 2015-09-23 16:09:47 -07:00
parent 9cbabd8fe5
commit 49a309ff6a
3 changed files with 416 additions and 59 deletions

View File

@ -25,34 +25,38 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <new>
#include <stdatomic.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <ctype.h>
#include <errno.h>
#include <poll.h>
#include <fcntl.h>
#include <poll.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <new>
#include <linux/xattr.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <sys/xattr.h>
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#include <sys/system_properties.h>
#include "private/bionic_futex.h"
#include "private/bionic_lock.h"
#include "private/bionic_macros.h"
#include "private/libc_logging.h"
static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
@ -192,7 +196,7 @@ struct find_nth_cookie {
}
};
static char property_filename[PATH_MAX] = PROP_FILENAME;
static char property_filename[PROP_FILENAME_MAX] = PROP_FILENAME;
static bool compat_mode = false;
static size_t pa_data_size;
static size_t pa_size;
@ -216,13 +220,12 @@ static int get_fd_from_env(void)
return atoi(env);
}
static int map_prop_area_rw()
{
static prop_area* map_prop_area_rw(const char* filename, const char* context,
bool* fsetxattr_failed) {
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
const int fd = open(property_filename,
O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
const int fd = open(filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
if (fd < 0) {
if (errno == EACCES) {
@ -231,12 +234,31 @@ static int map_prop_area_rw()
*/
abort();
}
return -1;
return nullptr;
}
if (context) {
if (fsetxattr(fd, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0) != 0) {
__libc_format_log(ANDROID_LOG_ERROR, "libc",
"fsetxattr failed to set context (%s) for \"%s\"", context, filename);
/*
* fsetxattr() will fail during system properties tests due to selinux policy.
* We do not want to create a custom policy for the tester, so we will continue in
* this function but set a flag that an error has occurred.
* Init, which is the only daemon that should ever call this function will abort
* when this error occurs.
* Otherwise, the tester will ignore it and continue, albeit without any selinux
* property separation.
*/
if (fsetxattr_failed) {
*fsetxattr_failed = true;
}
}
}
if (ftruncate(fd, PA_SIZE) < 0) {
close(fd);
return -1;
return nullptr;
}
pa_size = PA_SIZE;
@ -246,29 +268,26 @@ static int map_prop_area_rw()
void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (memory_area == MAP_FAILED) {
close(fd);
return -1;
return nullptr;
}
prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
/* plug into the lib property services */
__system_property_area__ = pa;
close(fd);
return 0;
return pa;
}
static int map_fd_ro(const int fd) {
static prop_area* map_fd_ro(const int fd) {
struct stat fd_stat;
if (fstat(fd, &fd_stat) < 0) {
return -1;
return nullptr;
}
if ((fd_stat.st_uid != 0)
|| (fd_stat.st_gid != 0)
|| ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0)
|| (fd_stat.st_size < static_cast<off_t>(sizeof(prop_area))) ) {
return -1;
return nullptr;
}
pa_size = fd_stat.st_size;
@ -276,7 +295,7 @@ static int map_fd_ro(const int fd) {
void* const map_result = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);
if (map_result == MAP_FAILED) {
return -1;
return nullptr;
}
prop_area* pa = reinterpret_cast<prop_area*>(map_result);
@ -284,22 +303,20 @@ static int map_fd_ro(const int fd) {
(pa->version() != PROP_AREA_VERSION &&
pa->version() != PROP_AREA_VERSION_COMPAT)) {
munmap(pa, pa_size);
return -1;
return nullptr;
}
if (pa->version() == PROP_AREA_VERSION_COMPAT) {
compat_mode = true;
}
__system_property_area__ = pa;
return 0;
return pa;
}
static int map_prop_area()
{
int fd = open(property_filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
static prop_area* map_prop_area(const char* filename, bool is_legacy) {
int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
bool close_fd = true;
if (fd == -1 && errno == ENOENT) {
if (fd == -1 && errno == ENOENT && is_legacy) {
/*
* For backwards compatibility, if the file doesn't
* exist, we use the environment to get the file descriptor.
@ -308,16 +325,18 @@ static int map_prop_area()
* returns other errors such as ENOMEM or ENFILE, since it
* might be possible for an external program to trigger this
* condition.
* Only do this for the legacy prop file, secured prop files
* do not have a backup
*/
fd = get_fd_from_env();
close_fd = false;
}
if (fd < 0) {
return -1;
return nullptr;
}
const int map_result = map_fd_ro(fd);
prop_area* map_result = map_fd_ro(fd);
if (close_fd) {
close(fd);
}
@ -623,9 +642,325 @@ bool prop_area::foreach(void (*propfn)(const prop_info* pi, void* cookie), void*
return foreach_property(root_node(), propfn, cookie);
}
struct context_node {
context_node(struct context_node* next, const char* context, prop_area* pa)
: context(strdup(context)), pa(pa), checked_access(false), next(next) {
lock.init(false);
}
~context_node() {
if (pa) {
munmap(pa, pa_size);
}
free(context);
}
Lock lock;
char* context;
prop_area* pa;
bool checked_access;
struct context_node* next;
};
struct prefix_node {
prefix_node(struct prefix_node* next, const char* prefix, context_node* context)
: prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) {
}
~prefix_node() {
free(prefix);
}
char* prefix;
const size_t prefix_len;
context_node* context;
struct prefix_node* next;
};
template <typename List, typename... Args>
static inline void list_add(List** list, Args... args) {
*list = new List(*list, args...);
}
static void list_add_after_len(prefix_node** list, const char* prefix, context_node* context) {
size_t prefix_len = strlen(prefix);
auto next_list = list;
while (*next_list) {
if ((*next_list)->prefix_len < prefix_len || (*next_list)->prefix[0] == '*') {
list_add(next_list, prefix, context);
return;
}
next_list = &(*next_list)->next;
}
list_add(next_list, prefix, context);
}
template <typename List, typename Func>
static void list_foreach(List* list, Func func) {
while (list) {
func(list);
list = list->next;
}
}
template <typename List, typename Func>
static List* list_find(List* list, Func func) {
while (list) {
if (func(list)) {
return list;
}
list = list->next;
}
return nullptr;
}
template <typename List>
static void list_free(List** list) {
while (*list) {
auto old_list = *list;
*list = old_list->next;
delete old_list;
}
}
static prefix_node* prefixes = nullptr;
static context_node* contexts = nullptr;
/*
* pthread_mutex_lock() calls into system_properties in the case of contention.
* This creates a risk of dead lock if any system_properties functions
* use pthread locks after system_property initialization.
*
* For this reason, the below three functions use a bionic Lock and static
* allocation of memory for each filename.
*/
static bool open_prop_file(context_node* cnode, bool access_rw, bool* fsetxattr_failed) {
cnode->lock.lock();
if (cnode->pa) {
cnode->lock.unlock();
return true;
}
char filename[PROP_FILENAME_MAX];
int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, cnode->context);
if (len < 0 || len > PROP_FILENAME_MAX) {
cnode->lock.unlock();
return false;
}
if (access_rw) {
cnode->pa = map_prop_area_rw(filename, cnode->context, fsetxattr_failed);
} else {
cnode->pa = map_prop_area(filename, false);
}
cnode->lock.unlock();
return cnode->pa;
}
static bool check_access(context_node* cnode) {
char filename[PROP_FILENAME_MAX];
int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, cnode->context);
if (len < 0 || len > PROP_FILENAME_MAX) {
return false;
}
return access(filename, R_OK) == 0;
}
static bool map_system_property_area(bool access_rw, bool* fsetxattr_failed) {
char filename[PROP_FILENAME_MAX];
int len = snprintf(filename, sizeof(filename), "%s/properties_serial", property_filename);
if (len < 0 || len > PROP_FILENAME_MAX) {
__system_property_area__ = nullptr;
return false;
}
if (access_rw) {
__system_property_area__ =
map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
} else {
__system_property_area__ = map_prop_area(filename, false);
}
return __system_property_area__;
}
static prop_area* get_prop_area_for_name(const char* name) {
auto entry = list_find(prefixes, [name](auto l) {
return l->prefix[0] == '*' || !strncmp(l->prefix, name, l->prefix_len);
});
if (!entry) {
return nullptr;
}
auto cnode = entry->context;
if (!cnode->pa) {
open_prop_file(cnode, false, nullptr);
}
return cnode->pa;
}
/*
* The below two functions are duplicated from label_support.c in libselinux.
* TODO: Find a location suitable for these functions such that both libc and
* libselinux can share a common source file.
*/
/*
* The read_spec_entries and read_spec_entry functions may be used to
* replace sscanf to read entries from spec files. The file and
* property services now use these.
*/
/* Read an entry from a spec file (e.g. file_contexts) */
static inline int read_spec_entry(char **entry, char **ptr, int *len)
{
*entry = NULL;
char *tmp_buf = NULL;
while (isspace(**ptr) && **ptr != '\0')
(*ptr)++;
tmp_buf = *ptr;
*len = 0;
while (!isspace(**ptr) && **ptr != '\0') {
(*ptr)++;
(*len)++;
}
if (*len) {
*entry = strndup(tmp_buf, *len);
if (!*entry)
return -1;
}
return 0;
}
/*
* line_buf - Buffer containing the spec entries .
* num_args - The number of spec parameter entries to process.
* ... - A 'char **spec_entry' for each parameter.
* returns - The number of items processed.
*
* This function calls read_spec_entry() to do the actual string processing.
*/
static int read_spec_entries(char *line_buf, int num_args, ...)
{
char **spec_entry, *buf_p;
int len, rc, items, entry_len = 0;
va_list ap;
len = strlen(line_buf);
if (line_buf[len - 1] == '\n')
line_buf[len - 1] = '\0';
else
/* Handle case if line not \n terminated by bumping
* the len for the check below (as the line is NUL
* terminated by getline(3)) */
len++;
buf_p = line_buf;
while (isspace(*buf_p))
buf_p++;
/* Skip comment lines and empty lines. */
if (*buf_p == '#' || *buf_p == '\0')
return 0;
/* Process the spec file entries */
va_start(ap, num_args);
items = 0;
while (items < num_args) {
spec_entry = va_arg(ap, char **);
if (len - 1 == buf_p - line_buf) {
va_end(ap);
return items;
}
rc = read_spec_entry(spec_entry, &buf_p, &entry_len);
if (rc < 0) {
va_end(ap);
return rc;
}
if (entry_len)
items++;
}
va_end(ap);
return items;
}
static bool initialize_properties() {
list_free(&prefixes);
list_free(&contexts);
FILE* file = fopen("/property_contexts", "re");
if (!file) {
return false;
}
char* buffer = nullptr;
size_t line_len;
char* prop_prefix = nullptr;
char* context = nullptr;
while (getline(&buffer, &line_len, file) > 0) {
int items = read_spec_entries(buffer, 2, &prop_prefix, &context);
if (items <= 0) {
continue;
}
if (items == 1) {
free(prop_prefix);
continue;
}
auto old_context =
list_find(contexts, [context](auto l) { return !strcmp(l->context, context); });
if (old_context) {
list_add_after_len(&prefixes, prop_prefix, old_context);
} else {
list_add(&contexts, context, nullptr);
list_add_after_len(&prefixes, prop_prefix, contexts);
}
free(prop_prefix);
free(context);
}
free(buffer);
fclose(file);
return true;
}
static bool is_dir(const char* pathname) {
struct stat info;
if (stat(pathname, &info) == -1) {
return false;
}
return S_ISDIR(info.st_mode);
}
int __system_properties_init()
{
return map_prop_area();
if (is_dir(property_filename)) {
if (!initialize_properties()) {
return -1;
}
if (!map_system_property_area(false, nullptr)) {
list_free(&prefixes);
list_free(&contexts);
return -1;
}
} else {
__system_property_area__ = map_prop_area(property_filename, true);
if (!__system_property_area__) {
return -1;
}
list_add(&contexts, "legacy_system_prop_area", __system_property_area__);
list_add_after_len(&prefixes, "*", contexts);
}
return 0;
}
int __system_property_set_filename(const char *filename)
@ -640,7 +975,23 @@ int __system_property_set_filename(const char *filename)
int __system_property_area_init()
{
return map_prop_area_rw();
mkdir(property_filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
if (!initialize_properties()) {
return -1;
}
bool open_prop_file_failed = false;
bool fsetxattr_failed = false;
list_foreach(contexts, [&fsetxattr_failed, &open_prop_file_failed](auto l) {
if (!open_prop_file(l, true, &fsetxattr_failed)) {
open_prop_file_failed = true;
}
});
if (open_prop_file_failed || !map_system_property_area(true, &fsetxattr_failed)) {
list_free(&prefixes);
list_free(&contexts);
return -1;
}
return fsetxattr_failed ? -2 : 0;
}
unsigned int __system_property_area_serial()
@ -659,11 +1010,13 @@ const prop_info *__system_property_find(const char *name)
return __system_property_find_compat(name);
}
if (!__system_property_area__) {
prop_area* pa = get_prop_area_for_name(name);
if (!pa) {
__libc_format_log(ANDROID_LOG_ERROR, "libc", "Access denied finding property \"%s\"", name);
return nullptr;
}
return __system_property_area__->find(name);
return pa->find(name);
}
// The C11 standard doesn't allow atomic loads from const fields,
@ -769,8 +1122,6 @@ int __system_property_update(prop_info *pi, const char *value, unsigned int len)
int __system_property_add(const char *name, unsigned int namelen,
const char *value, unsigned int valuelen)
{
prop_area *pa = __system_property_area__;
if (namelen >= PROP_NAME_MAX)
return -1;
if (valuelen >= PROP_VALUE_MAX)
@ -778,21 +1129,24 @@ int __system_property_add(const char *name, unsigned int namelen,
if (namelen < 1)
return -1;
if (!__system_property_area__) {
prop_area* pa = get_prop_area_for_name(name);
if (!pa) {
__libc_format_log(ANDROID_LOG_ERROR, "libc", "Access denied adding property \"%s\"", name);
return -1;
}
bool ret = __system_property_area__->add(name, namelen, value, valuelen);
bool ret = pa->add(name, namelen, value, valuelen);
if (!ret)
return -1;
// There is only a single mutator, but we want to make sure that
// updates are visible to a reader waiting for the update.
atomic_store_explicit(
pa->serial(),
atomic_load_explicit(pa->serial(), memory_order_relaxed) + 1,
__system_property_area__->serial(),
atomic_load_explicit(__system_property_area__->serial(), memory_order_relaxed) + 1,
memory_order_release);
__futex_wake(pa->serial(), INT32_MAX);
__futex_wake(__system_property_area__->serial(), INT32_MAX);
return 0;
}
@ -841,9 +1195,16 @@ int __system_property_foreach(void (*propfn)(const prop_info *pi, void *cookie),
return __system_property_foreach_compat(propfn, cookie);
}
if (!__system_property_area__) {
return -1;
}
return __system_property_area__->foreach(propfn, cookie) ? 0 : -1;
list_foreach(contexts, [propfn, cookie](auto l) {
if (!l->pa && !l->checked_access) {
if (check_access(l)) {
open_prop_file(l, false, nullptr);
}
l->checked_access = true;
}
if (l->pa) {
l->pa->foreach(propfn, cookie);
}
});
return 0;
}

View File

@ -41,6 +41,7 @@ typedef struct prop_msg prop_msg;
#define PROP_AREA_VERSION_COMPAT 0x45434f76
#define PROP_SERVICE_NAME "property_service"
#define PROP_FILENAME_MAX 1024
#define PROP_FILENAME "/dev/__properties__"
#define PA_SIZE (128 * 1024)

View File

@ -41,9 +41,6 @@ struct LocalPropertyTestState {
return;
}
old_pa = __system_property_area__;
__system_property_area__ = NULL;
pa_dirname = dirname;
pa_filename = pa_dirname + "/__properties__";
@ -57,9 +54,8 @@ struct LocalPropertyTestState {
return;
}
__system_property_area__ = old_pa;
__system_property_set_filename(PROP_FILENAME);
__system_properties_init();
unlink(pa_filename.c_str());
rmdir(pa_dirname.c_str());
}
@ -68,7 +64,6 @@ public:
private:
std::string pa_dirname;
std::string pa_filename;
void *old_pa;
};
static void foreach_test_callback(const prop_info *pi, void* cookie) {