platform_bionic/libc/system_properties/contexts_split.cpp
Jeff Vander Stoep 70aa88c66c Remove references to nonplat sepolicy
"nonplat" was renamed to "vendor" in Android Pie, but was retained
here for Treble compatibility.

We're now outside of the compatbility window for these devices so
it can safely be removed.

Test: build boot cuttlefish device. adb remount, modify
/system/etc/selinux/plat_sepolicy_and_mapping.sha256 to force
on-device policy compilation. reboot. Verify that device boots
without new selinux denials.

Change-Id: I663a524670120ee19dfe785aa5f89b3981bdd378
2021-11-05 09:30:25 +01:00

361 lines
10 KiB
C++

/*
* Copyright (C) 2008 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "system_properties/contexts_split.h"
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <async_safe/log.h>
#include "system_properties/context_node.h"
#include "system_properties/system_properties.h"
class ContextListNode : public ContextNode {
public:
ContextListNode(ContextListNode* next, const char* context, const char* filename)
: ContextNode(strdup(context), filename), next(next) {
}
~ContextListNode() {
free(const_cast<char*>(context()));
}
ContextListNode* next;
};
struct PrefixNode {
PrefixNode(struct PrefixNode* next, const char* prefix, ContextListNode* context)
: prefix(strdup(prefix)), prefix_len(strlen(prefix)), context(context), next(next) {
}
~PrefixNode() {
free(prefix);
}
char* prefix;
const size_t prefix_len;
ContextListNode* context;
PrefixNode* next;
};
template <typename List, typename... Args>
static inline void ListAdd(List** list, Args... args) {
*list = new List(*list, args...);
}
static void ListAddAfterLen(PrefixNode** list, const char* prefix, ContextListNode* 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] == '*') {
ListAdd(next_list, prefix, context);
return;
}
next_list = &(*next_list)->next;
}
ListAdd(next_list, prefix, context);
}
template <typename List, typename Func>
static void ListForEach(List* list, Func func) {
while (list) {
func(list);
list = list->next;
}
}
template <typename List, typename Func>
static List* ListFind(List* list, Func func) {
while (list) {
if (func(list)) {
return list;
}
list = list->next;
}
return nullptr;
}
template <typename List>
static void ListFree(List** list) {
while (*list) {
auto old_list = *list;
*list = old_list->next;
delete old_list;
}
}
// The below two functions are duplicated from label_support.c in libselinux.
// 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 = nullptr;
char* tmp_buf = nullptr;
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;
}
bool ContextsSplit::MapSerialPropertyArea(bool access_rw, bool* fsetxattr_failed) {
char filename[PROP_FILENAME_MAX];
int len = async_safe_format_buffer(filename, sizeof(filename), "%s/properties_serial", filename_);
if (len < 0 || len >= PROP_FILENAME_MAX) {
serial_prop_area_ = nullptr;
return false;
}
if (access_rw) {
serial_prop_area_ =
prop_area::map_prop_area_rw(filename, "u:object_r:properties_serial:s0", fsetxattr_failed);
} else {
serial_prop_area_ = prop_area::map_prop_area(filename);
}
return serial_prop_area_;
}
bool ContextsSplit::InitializePropertiesFromFile(const char* filename) {
FILE* file = fopen(filename, "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;
}
// init uses ctl.* properties as an IPC mechanism and does not write them
// to a property file, therefore we do not need to create property files
// to store them.
if (!strncmp(prop_prefix, "ctl.", 4)) {
free(prop_prefix);
free(context);
continue;
}
auto old_context = ListFind(
contexts_, [context](ContextListNode* l) { return !strcmp(l->context(), context); });
if (old_context) {
ListAddAfterLen(&prefixes_, prop_prefix, old_context);
} else {
ListAdd(&contexts_, context, filename_);
ListAddAfterLen(&prefixes_, prop_prefix, contexts_);
}
free(prop_prefix);
free(context);
}
free(buffer);
fclose(file);
return true;
}
bool ContextsSplit::InitializeProperties() {
// If we do find /property_contexts, then this is being
// run as part of the OTA updater on older release that had
// /property_contexts - b/34370523
if (InitializePropertiesFromFile("/property_contexts")) {
return true;
}
// Use property_contexts from /system & /vendor, fall back to those from /
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
if (!InitializePropertiesFromFile("/system/etc/selinux/plat_property_contexts")) {
return false;
}
// Don't check for failure here, since we don't always have all of these partitions.
// E.g. In case of recovery, the vendor partition will not have mounted and we
// still need the system / platform properties to function.
if (access("/vendor/etc/selinux/vendor_property_contexts", R_OK) != -1) {
InitializePropertiesFromFile("/vendor/etc/selinux/vendor_property_contexts");
}
} else {
if (!InitializePropertiesFromFile("/plat_property_contexts")) {
return false;
}
if (access("/vendor_property_contexts", R_OK) != -1) {
InitializePropertiesFromFile("/vendor_property_contexts");
}
}
return true;
}
bool ContextsSplit::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
filename_ = filename;
if (!InitializeProperties()) {
return false;
}
if (writable) {
mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
bool open_failed = false;
if (fsetxattr_failed) {
*fsetxattr_failed = false;
}
ListForEach(contexts_, [&fsetxattr_failed, &open_failed](ContextListNode* l) {
if (!l->Open(true, fsetxattr_failed)) {
open_failed = true;
}
});
if (open_failed || !MapSerialPropertyArea(true, fsetxattr_failed)) {
FreeAndUnmap();
return false;
}
} else {
if (!MapSerialPropertyArea(false, nullptr)) {
FreeAndUnmap();
return false;
}
}
return true;
}
PrefixNode* ContextsSplit::GetPrefixNodeForName(const char* name) {
auto entry = ListFind(prefixes_, [name](PrefixNode* l) {
return l->prefix[0] == '*' || !strncmp(l->prefix, name, l->prefix_len);
});
return entry;
}
prop_area* ContextsSplit::GetPropAreaForName(const char* name) {
auto entry = GetPrefixNodeForName(name);
if (!entry) {
return nullptr;
}
auto cnode = entry->context;
if (!cnode->pa()) {
// We explicitly do not check no_access_ in this case because unlike the
// case of foreach(), we want to generate an selinux audit for each
// non-permitted property access in this function.
cnode->Open(false, nullptr);
}
return cnode->pa();
}
void ContextsSplit::ForEach(void (*propfn)(const prop_info* pi, void* cookie), void* cookie) {
ListForEach(contexts_, [propfn, cookie](ContextListNode* l) {
if (l->CheckAccessAndOpen()) {
l->pa()->foreach (propfn, cookie);
}
});
}
void ContextsSplit::ResetAccess() {
ListForEach(contexts_, [](ContextListNode* l) { l->ResetAccess(); });
}
void ContextsSplit::FreeAndUnmap() {
ListFree(&prefixes_);
ListFree(&contexts_);
prop_area::unmap_prop_area(&serial_prop_area_);
}