Reset access to system properties on reinitialization

Treat subsequent calls to __system_properties_init() as a
reinitialization of system properties and revoke access to prop files
that have been previously mapped but that the process's current context
does not have access to.  Additionally reset the no_access_ flag in
case permissions have loosened and previously unaccessible files can now
be accessed.

This is meant to work around an issue that setcon() does not revoke
mmap() mappings, so we must manually revoke them after a successful
setcon() call.

Bug 26114086

Change-Id: I4d690abb6817283ca64ac26ea4c1dad398a98fbc
diff --git a/libc/bionic/system_properties.cpp b/libc/bionic/system_properties.cpp
index b9a373e..2720455 100644
--- a/libc/bionic/system_properties.cpp
+++ b/libc/bionic/system_properties.cpp
@@ -200,6 +200,7 @@
 static bool compat_mode = false;
 static size_t pa_data_size;
 static size_t pa_size;
+static bool initialized = false;
 
 // NOTE: This isn't static because system_properties_compat.c
 // requires it.
@@ -642,22 +643,33 @@
     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);
+class context_node {
+public:
+    context_node(context_node* next, const char* context, prop_area* pa)
+        : next(next), context_(strdup(context)), pa_(pa), no_access_(false) {
+        lock_.init(false);
     }
     ~context_node() {
-        if (pa) {
-            munmap(pa, pa_size);
-        }
-        free(context);
+        unmap();
+        free(context_);
     }
-    Lock lock;
-    char* context;
-    prop_area* pa;
-    bool checked_access;
-    struct context_node* next;
+    bool open(bool access_rw, bool* fsetxattr_failed);
+    bool check_access_and_open();
+    void reset_access();
+
+    const char* context() const { return context_; }
+    prop_area* pa() { return pa_; }
+
+    context_node* next;
+
+private:
+    bool check_access();
+    void unmap();
+
+    Lock lock_;
+    char* context_;
+    prop_area* pa_;
+    bool no_access_;
 };
 
 struct prefix_node {
@@ -733,32 +745,50 @@
  * 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();
+bool context_node::open(bool access_rw, bool* fsetxattr_failed) {
+    lock_.lock();
+    if (pa_) {
+        lock_.unlock();
         return true;
     }
 
     char filename[PROP_FILENAME_MAX];
-    int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, cnode->context);
+    int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, context_);
     if (len < 0 || len > PROP_FILENAME_MAX) {
-        cnode->lock.unlock();
+        lock_.unlock();
         return false;
     }
 
     if (access_rw) {
-        cnode->pa = map_prop_area_rw(filename, cnode->context, fsetxattr_failed);
+        pa_ = map_prop_area_rw(filename, context_, fsetxattr_failed);
     } else {
-        cnode->pa = map_prop_area(filename, false);
+        pa_ = map_prop_area(filename, false);
     }
-    cnode->lock.unlock();
-    return cnode->pa;
+    lock_.unlock();
+    return pa_;
 }
 
-static bool check_access(context_node* cnode) {
+bool context_node::check_access_and_open() {
+    if (!pa_ && !no_access_) {
+        if (!check_access() || !open(false, nullptr)) {
+            no_access_ = true;
+        }
+    }
+    return pa_;
+}
+
+void context_node::reset_access() {
+    if (!check_access()) {
+        unmap();
+        no_access_ = true;
+    } else {
+        no_access_ = false;
+    }
+}
+
+bool context_node::check_access() {
     char filename[PROP_FILENAME_MAX];
-    int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, cnode->context);
+    int len = snprintf(filename, sizeof(filename), "%s/%s", property_filename, context_);
     if (len < 0 || len > PROP_FILENAME_MAX) {
         return false;
     }
@@ -766,6 +796,18 @@
     return access(filename, R_OK) == 0;
 }
 
+void context_node::unmap() {
+    if (!pa_) {
+        return;
+    }
+
+    munmap(pa_, pa_size);
+    if (pa_ == __system_property_area__) {
+        __system_property_area__ = nullptr;
+    }
+    pa_ = nullptr;
+}
+
 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);
@@ -792,10 +834,15 @@
     }
 
     auto cnode = entry->context;
-    if (!cnode->pa) {
-        open_prop_file(cnode, false, nullptr);
+    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;
+    return cnode->pa();
 }
 
 /*
@@ -892,9 +939,6 @@
 }
 
 static bool initialize_properties() {
-    list_free(&prefixes);
-    list_free(&contexts);
-
     FILE* file = fopen("/property_contexts", "re");
 
     if (!file) {
@@ -927,7 +971,7 @@
         }
 
         auto old_context = list_find(
-            contexts, [context](context_node* l) { return !strcmp(l->context, context); });
+            contexts, [context](context_node* l) { return !strcmp(l->context(), context); });
         if (old_context) {
             list_add_after_len(&prefixes, prop_prefix, old_context);
         } else {
@@ -951,15 +995,27 @@
     return S_ISDIR(info.st_mode);
 }
 
+static void free_and_unmap_contexts() {
+    list_free(&prefixes);
+    list_free(&contexts);
+    if (__system_property_area__) {
+        munmap(__system_property_area__, pa_size);
+        __system_property_area__ = nullptr;
+    }
+}
+
 int __system_properties_init()
 {
+    if (initialized) {
+        list_foreach(contexts, [](context_node* l) { l->reset_access(); });
+        return 0;
+    }
     if (is_dir(property_filename)) {
         if (!initialize_properties()) {
             return -1;
         }
         if (!map_system_property_area(false, nullptr)) {
-            list_free(&prefixes);
-            list_free(&contexts);
+            free_and_unmap_contexts();
             return -1;
         }
     } else {
@@ -970,6 +1026,7 @@
         list_add(&contexts, "legacy_system_prop_area", __system_property_area__);
         list_add_after_len(&prefixes, "*", contexts);
     }
+    initialized = true;
     return 0;
 }
 
@@ -985,22 +1042,23 @@
 
 int __system_property_area_init()
 {
+    free_and_unmap_contexts();
     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 open_failed = false;
     bool fsetxattr_failed = false;
-    list_foreach(contexts, [&fsetxattr_failed, &open_prop_file_failed](context_node* l) {
-        if (!open_prop_file(l, true, &fsetxattr_failed)) {
-            open_prop_file_failed = true;
+    list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
+        if (!l->open(true, &fsetxattr_failed)) {
+            open_failed = true;
         }
     });
-    if (open_prop_file_failed || !map_system_property_area(true, &fsetxattr_failed)) {
-        list_free(&prefixes);
-        list_free(&contexts);
+    if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {
+        free_and_unmap_contexts();
         return -1;
     }
+    initialized = true;
     return fsetxattr_failed ? -2 : 0;
 }
 
@@ -1226,14 +1284,8 @@
     }
 
     list_foreach(contexts, [propfn, cookie](context_node* 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);
+        if (l->check_access_and_open()) {
+            l->pa()->foreach(propfn, cookie);
         }
     });
     return 0;