| /* | |
| * 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) { | |
| PropertiesFilename filename(filename_, "properties_serial"); | |
| if (access_rw) { | |
| serial_prop_area_ = prop_area::map_prop_area_rw( | |
| filename.c_str(), "u:object_r:properties_serial:s0", fsetxattr_failed); | |
| } else { | |
| serial_prop_area_ = prop_area::map_prop_area(filename.c_str()); | |
| } | |
| 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, bool) { | |
| 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_); | |
| } |