Merge "Separate properties by selabel"

am: 376b94f1e7

* commit '376b94f1e777375eb9ebb04a882b898aafa94625':
  Separate properties by selabel
This commit is contained in:
Tom Cherry 2015-12-03 22:58:48 +00:00 committed by android-build-merger
commit 84bc4f5cc7
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) {