Merge "Introduce property types" am: a0ffad60ef
am: 3657c80c79

Change-Id: Ifb5916d3ed40d44acf89e7a55b39a3d94f0908f9
diff --git a/init/Android.bp b/init/Android.bp
index 2fea359..0db7824 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -75,6 +75,7 @@
         "persistent_properties.cpp",
         "persistent_properties.proto",
         "property_service.cpp",
+        "property_type.cpp",
         "security.cpp",
         "selinux.cpp",
         "service.cpp",
@@ -178,6 +179,7 @@
         "init_test.cpp",
         "persistent_properties_test.cpp",
         "property_service_test.cpp",
+        "property_type_test.cpp",
         "result_test.cpp",
         "rlimit_parser_test.cpp",
         "service_test.cpp",
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 7aa94b0..79f7f25 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -58,6 +58,7 @@
 
 #include "init.h"
 #include "persistent_properties.h"
+#include "property_type.h"
 #include "util.h"
 
 using android::base::ReadFileToString;
@@ -95,14 +96,9 @@
         LOG(FATAL) << "Failed to load serialized property info file";
     }
 }
-static bool check_mac_perms(const std::string& name, char* sctx, struct ucred* cr) {
-    if (!sctx) {
-      return false;
-    }
-
-    const char* target_context = nullptr;
-    property_info_area->GetPropertyInfo(name.c_str(), &target_context, nullptr);
-    if (target_context == nullptr) {
+static bool CheckMacPerms(const std::string& name, const char* target_context,
+                          const char* source_context, struct ucred* cr) {
+    if (!target_context || !source_context) {
         return false;
     }
 
@@ -111,29 +107,12 @@
     audit_data.name = name.c_str();
     audit_data.cr = cr;
 
-    bool has_access =
-        (selinux_check_access(sctx, target_context, "property_service", "set", &audit_data) == 0);
+    bool has_access = (selinux_check_access(source_context, target_context, "property_service",
+                                            "set", &audit_data) == 0);
 
     return has_access;
 }
 
-static int check_control_mac_perms(const char *name, char *sctx, struct ucred *cr)
-{
-    /*
-     *  Create a name prefix out of ctl.<service name>
-     *  The new prefix allows the use of the existing
-     *  property service backend labeling while avoiding
-     *  mislabels based on true property prefixes.
-     */
-    char ctl_name[PROP_VALUE_MAX+4];
-    int ret = snprintf(ctl_name, sizeof(ctl_name), "ctl.%s", name);
-
-    if (ret < 0 || (size_t) ret >= sizeof(ctl_name))
-        return 0;
-
-    return check_mac_perms(ctl_name, sctx, cr);
-}
-
 bool is_legal_property_name(const std::string& name) {
     size_t namelen = name.size();
 
@@ -422,52 +401,70 @@
   struct ucred cr = socket.cred();
   char* source_ctx = nullptr;
   getpeercon(socket.socket(), &source_ctx);
+  std::string source_context = source_ctx;
+  freecon(source_ctx);
 
   if (StartsWith(name, "ctl.")) {
-    if (check_control_mac_perms(value.c_str(), source_ctx, &cr)) {
+      // ctl. properties have their name ctl.<action> and their value is the name of the service to
+      // apply that action to.  Permissions for these actions are based on the service, so we must
+      // create a fake name of ctl.<service> to check permissions.
+      auto control_string = "ctl." + value;
+      const char* target_context = nullptr;
+      const char* type = nullptr;
+      property_info_area->GetPropertyInfo(control_string.c_str(), &target_context, &type);
+      if (!CheckMacPerms(control_string, target_context, source_context.c_str(), &cr)) {
+          LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
+                     << " service ctl [" << value << "]"
+                     << " uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid;
+          if (!legacy_protocol) {
+              socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
+          }
+          return;
+      }
+
       handle_control_message(name.c_str() + 4, value.c_str());
       if (!legacy_protocol) {
-        socket.SendUint32(PROP_SUCCESS);
+          socket.SendUint32(PROP_SUCCESS);
       }
-    } else {
-      LOG(ERROR) << "sys_prop(" << cmd_name << "): Unable to " << (name.c_str() + 4)
-                 << " service ctl [" << value << "]"
-                 << " uid:" << cr.uid
-                 << " gid:" << cr.gid
-                 << " pid:" << cr.pid;
-      if (!legacy_protocol) {
-        socket.SendUint32(PROP_ERROR_HANDLE_CONTROL_MESSAGE);
-      }
-    }
   } else {
-    if (check_mac_perms(name, source_ctx, &cr)) {
+      const char* target_context = nullptr;
+      const char* type = nullptr;
+      property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
+      if (!CheckMacPerms(name, target_context, source_context.c_str(), &cr)) {
+          LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid
+                     << " name:" << name;
+          if (!legacy_protocol) {
+              socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
+          }
+          return;
+      }
+      if (type == nullptr || !CheckType(type, value)) {
+          LOG(ERROR) << "sys_prop(" << cmd_name << "): type check failed, type: '"
+                     << (type ?: "(null)") << "' value: '" << value << "'";
+          if (!legacy_protocol) {
+              socket.SendUint32(PROP_ERROR_INVALID_VALUE);
+          }
+          return;
+      }
       // sys.powerctl is a special property that is used to make the device reboot.  We want to log
       // any process that sets this property to be able to accurately blame the cause of a shutdown.
       if (name == "sys.powerctl") {
-        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
-        std::string process_cmdline;
-        std::string process_log_string;
-        if (ReadFileToString(cmdline_path, &process_cmdline)) {
-          // Since cmdline is null deliminated, .c_str() conveniently gives us just the process path.
-          process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
-        }
-        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
-                  << process_log_string;
+          std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
+          std::string process_cmdline;
+          std::string process_log_string;
+          if (ReadFileToString(cmdline_path, &process_cmdline)) {
+              // Since cmdline is null deliminated, .c_str() conveniently gives us just the process path.
+              process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
+          }
+          LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
+                    << process_log_string;
       }
 
       uint32_t result = property_set(name, value);
       if (!legacy_protocol) {
-        socket.SendUint32(result);
+          socket.SendUint32(result);
       }
-    } else {
-      LOG(ERROR) << "sys_prop(" << cmd_name << "): permission denied uid:" << cr.uid << " name:" << name;
-      if (!legacy_protocol) {
-        socket.SendUint32(PROP_ERROR_PERMISSION_DENIED);
-      }
-    }
   }
-
-  freecon(source_ctx);
 }
 
 static void handle_property_set_fd() {
@@ -764,9 +761,10 @@
         }
         LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
     }
+
     auto serialized_contexts = std::string();
     auto error = std::string();
-    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "\\s*", &serialized_contexts,
+    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                    &error)) {
         LOG(ERROR) << "Unable to serialize property contexts: " << error;
         return;
diff --git a/init/property_type.cpp b/init/property_type.cpp
new file mode 100644
index 0000000..249b12b
--- /dev/null
+++ b/init/property_type.cpp
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "property_type.h"
+
+#include <android-base/parsedouble.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+using android::base::ParseDouble;
+using android::base::ParseInt;
+using android::base::ParseUint;
+using android::base::Split;
+
+namespace android {
+namespace init {
+
+bool CheckType(const std::string& type_string, const std::string& value) {
+    auto type_strings = Split(type_string, " ");
+    if (type_strings.empty()) {
+        return false;
+    }
+    auto type = type_strings[0];
+
+    if (type == "string") {
+        return true;
+    }
+    if (type == "bool") {
+        return value == "true" || value == "false" || value == "1" || value == "0";
+    }
+    if (type == "int") {
+        int64_t parsed;
+        return ParseInt(value, &parsed);
+    }
+    if (type == "uint") {
+        uint64_t parsed;
+        if (value.empty() || value.front() == '-') {
+            return false;
+        }
+        return ParseUint(value, &parsed);
+    }
+    if (type == "double") {
+        double parsed;
+        return ParseDouble(value.c_str(), &parsed);
+    }
+    if (type == "size") {
+        auto it = value.begin();
+        while (it != value.end() && isdigit(*it)) {
+            it++;
+        }
+        if (it == value.begin() || it == value.end() || (*it != 'g' && *it != 'k' && *it != 'm')) {
+            return false;
+        }
+        it++;
+        return it == value.end();
+    }
+    if (type == "enum") {
+        for (auto it = std::next(type_strings.begin()); it != type_strings.end(); ++it) {
+            if (*it == value) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/property_type.h b/init/property_type.h
new file mode 100644
index 0000000..c889e16
--- /dev/null
+++ b/init/property_type.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _INIT_PROPERTY_TYPE_H
+#define _INIT_PROPERTY_TYPE_H
+
+#include <string>
+
+namespace android {
+namespace init {
+
+bool CheckType(const std::string& type_string, const std::string& value);
+
+}  // namespace init
+}  // namespace android
+
+#endif
diff --git a/init/property_type_test.cpp b/init/property_type_test.cpp
new file mode 100644
index 0000000..068bccc
--- /dev/null
+++ b/init/property_type_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "property_type.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace init {
+
+TEST(property_type, CheckType_string) {
+    EXPECT_TRUE(CheckType("string", ""));
+    EXPECT_TRUE(CheckType("string", "-234"));
+    EXPECT_TRUE(CheckType("string", "234"));
+    EXPECT_TRUE(CheckType("string", "true"));
+    EXPECT_TRUE(CheckType("string", "false"));
+    EXPECT_TRUE(CheckType("string", "45645634563456345634563456"));
+    EXPECT_TRUE(CheckType("string", "some other string"));
+}
+
+TEST(property_type, CheckType_int) {
+    EXPECT_FALSE(CheckType("int", ""));
+    EXPECT_FALSE(CheckType("int", "abc"));
+    EXPECT_FALSE(CheckType("int", "-abc"));
+    EXPECT_TRUE(CheckType("int", "0"));
+    EXPECT_TRUE(CheckType("int", std::to_string(std::numeric_limits<int64_t>::min())));
+    EXPECT_TRUE(CheckType("int", std::to_string(std::numeric_limits<int64_t>::max())));
+    EXPECT_TRUE(CheckType("int", "123"));
+    EXPECT_TRUE(CheckType("int", "-123"));
+}
+
+TEST(property_type, CheckType_uint) {
+    EXPECT_FALSE(CheckType("uint", ""));
+    EXPECT_FALSE(CheckType("uint", "abc"));
+    EXPECT_FALSE(CheckType("uint", "-abc"));
+    EXPECT_TRUE(CheckType("uint", "0"));
+    EXPECT_TRUE(CheckType("uint", std::to_string(std::numeric_limits<uint64_t>::max())));
+    EXPECT_TRUE(CheckType("uint", "123"));
+    EXPECT_FALSE(CheckType("uint", "-123"));
+}
+
+TEST(property_type, CheckType_double) {
+    EXPECT_FALSE(CheckType("double", ""));
+    EXPECT_FALSE(CheckType("double", "abc"));
+    EXPECT_FALSE(CheckType("double", "-abc"));
+    EXPECT_TRUE(CheckType("double", "0.0"));
+    EXPECT_TRUE(CheckType("double", std::to_string(std::numeric_limits<double>::min())));
+    EXPECT_TRUE(CheckType("double", std::to_string(std::numeric_limits<double>::max())));
+    EXPECT_TRUE(CheckType("double", "123.1"));
+    EXPECT_TRUE(CheckType("double", "-123.1"));
+}
+
+TEST(property_type, CheckType_size) {
+    EXPECT_FALSE(CheckType("size", ""));
+    EXPECT_FALSE(CheckType("size", "ab"));
+    EXPECT_FALSE(CheckType("size", "abcd"));
+    EXPECT_FALSE(CheckType("size", "0"));
+
+    EXPECT_TRUE(CheckType("size", "512g"));
+    EXPECT_TRUE(CheckType("size", "512k"));
+    EXPECT_TRUE(CheckType("size", "512m"));
+
+    EXPECT_FALSE(CheckType("size", "512gggg"));
+    EXPECT_FALSE(CheckType("size", "512mgk"));
+    EXPECT_FALSE(CheckType("size", "g"));
+    EXPECT_FALSE(CheckType("size", "m"));
+}
+
+TEST(property_type, CheckType_enum) {
+    EXPECT_FALSE(CheckType("enum abc", ""));
+    EXPECT_FALSE(CheckType("enum abc", "ab"));
+    EXPECT_FALSE(CheckType("enum abc", "abcd"));
+    EXPECT_FALSE(CheckType("enum 123 456 789", "0"));
+
+    EXPECT_TRUE(CheckType("enum abc", "abc"));
+    EXPECT_TRUE(CheckType("enum 123 456 789", "123"));
+    EXPECT_TRUE(CheckType("enum 123 456 789", "456"));
+    EXPECT_TRUE(CheckType("enum 123 456 789", "789"));
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/property_service/libpropertyinfoserializer/property_info_file.cpp b/property_service/libpropertyinfoserializer/property_info_file.cpp
index bf96d88..2cdc62d 100644
--- a/property_service/libpropertyinfoserializer/property_info_file.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_file.cpp
@@ -1,9 +1,26 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 #include <property_info_serializer/property_info_serializer.h>
 
 #include <android-base/strings.h>
 
 #include "space_tokenizer.h"
 
+using android::base::Join;
 using android::base::Split;
 using android::base::StartsWith;
 using android::base::Trim;
@@ -11,6 +28,34 @@
 namespace android {
 namespace properties {
 
+namespace {
+
+bool IsTypeValid(const std::vector<std::string>& type_strings) {
+  if (type_strings.empty()) {
+    return false;
+  }
+
+  // There must be at least one string following 'enum'
+  if (type_strings[0] == "enum") {
+    return type_strings.size() > 1;
+  }
+
+  // There should not be any string following any other types.
+  if (type_strings.size() != 1) {
+    return false;
+  }
+
+  // Check that the type matches one of remaining valid types.
+  static const char* const no_parameter_types[] = {"string", "bool",   "int",
+                                                   "uint",   "double", "size"};
+  for (const auto& type : no_parameter_types) {
+    if (type_strings[0] == type) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {
   auto tokenizer = SpaceTokenizer(line);
 
@@ -26,14 +71,28 @@
     return false;
   }
 
-  // It is not an error to not find these, as older files will not contain them.
+  // It is not an error to not find exact_match or a type, as older files will not contain them.
   auto exact_match = tokenizer.GetNext();
-  auto type = tokenizer.GetRemaining();
+  // We reformat type to be space deliminated regardless of the input whitespace for easier storage
+  // and subsequent parsing.
+  auto type_strings = std::vector<std::string>{};
+  auto type = tokenizer.GetNext();
+  while (!type.empty()) {
+    type_strings.emplace_back(type);
+    type = tokenizer.GetNext();
+  }
 
-  *out = {property, context, type, exact_match == "exact"};
+  if (!type_strings.empty() && !IsTypeValid(type_strings)) {
+    *error = "Type '" + Join(type_strings, " ") + "' is not valid";
+    return false;
+  }
+
+  *out = {property, context, Join(type_strings, " "), exact_match == "exact"};
   return true;
 }
 
+}  // namespace
+
 void ParsePropertyInfoFile(const std::string& file_contents,
                            std::vector<PropertyInfoEntry>* property_infos,
                            std::vector<std::string>* errors) {