Support bootconfig in first stage init and fs_mgr

Androidboot parameters are being moved from the kernel commandline to
bootconfig.
fs_mgr looks for these parameters in properties and falls back to
reading directly from /proc/cmdline. So both of these sources are
updated for bootconfig.
The androidboot parameters from /proc/bootconfig
are added as ro.boot properties, and fs_mgr will fall back to searching
/proc/bootconfig if it is too early.

Test: boot cuttlefish with androidboot.fstab_suffix and
androidboot.hardware in bootconfig and not in cmdline.
Test: atest CtsFsMgrTestCases
Bug: 173815685

Change-Id: Iea36a0da94c26e1aa37d97c576725e0ad77cd3ad
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index abece4d..75d1e0d 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -26,7 +26,7 @@
 
 #include "fs_mgr_priv.h"
 
-std::vector<std::pair<std::string, std::string>> fs_mgr_parse_boot_config(const std::string& cmdline) {
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_cmdline(const std::string& cmdline) {
     static constexpr char quote = '"';
 
     std::vector<std::pair<std::string, std::string>> result;
@@ -60,12 +60,50 @@
     return result;
 }
 
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_proc_bootconfig(
+        const std::string& cmdline) {
+    static constexpr char quote = '"';
+
+    std::vector<std::pair<std::string, std::string>> result;
+    for (auto& line : android::base::Split(cmdline, "\n")) {
+        line.erase(std::remove(line.begin(), line.end(), quote), line.end());
+        auto equal_sign = line.find('=');
+        if (equal_sign == line.npos) {
+            if (!line.empty()) {
+                // no difference between <key> and <key>=
+                result.emplace_back(std::move(line), "");
+            }
+        } else {
+            result.emplace_back(android::base::Trim(line.substr(0, equal_sign)),
+                                android::base::Trim(line.substr(equal_sign + 1)));
+        }
+    }
+
+    return result;
+}
+
+bool fs_mgr_get_boot_config_from_bootconfig(const std::string& bootconfig,
+                                            const std::string& android_key, std::string* out_val) {
+    FS_MGR_CHECK(out_val != nullptr);
+
+    const std::string bootconfig_key("androidboot." + android_key);
+    for (const auto& [key, value] : fs_mgr_parse_proc_bootconfig(bootconfig)) {
+        if (key == bootconfig_key) {
+            *out_val = value;
+            return true;
+        }
+    }
+
+    *out_val = "";
+    return false;
+}
+
 bool fs_mgr_get_boot_config_from_kernel(const std::string& cmdline, const std::string& android_key,
                                         std::string* out_val) {
     FS_MGR_CHECK(out_val != nullptr);
 
     const std::string cmdline_key("androidboot." + android_key);
-    for (const auto& [key, value] : fs_mgr_parse_boot_config(cmdline)) {
+    for (const auto& [key, value] : fs_mgr_parse_cmdline(cmdline)) {
         if (key == cmdline_key) {
             *out_val = value;
             return true;
@@ -76,6 +114,17 @@
     return false;
 }
 
+// Tries to get the given boot config value from bootconfig.
+// Returns true if successfully found, false otherwise.
+bool fs_mgr_get_boot_config_from_bootconfig_source(const std::string& key, std::string* out_val) {
+    std::string bootconfig;
+    if (!android::base::ReadFileToString("/proc/bootconfig", &bootconfig)) return false;
+    if (!bootconfig.empty() && bootconfig.back() == '\n') {
+        bootconfig.pop_back();
+    }
+    return fs_mgr_get_boot_config_from_bootconfig(bootconfig, key, out_val);
+}
+
 // Tries to get the given boot config value from kernel cmdline.
 // Returns true if successfully found, false otherwise.
 bool fs_mgr_get_boot_config_from_kernel_cmdline(const std::string& key, std::string* out_val) {
@@ -110,6 +159,11 @@
         return true;
     }
 
+    // next, check if we have the property in bootconfig
+    if (fs_mgr_get_boot_config_from_bootconfig_source(key, out_val)) {
+        return true;
+    }
+
     // finally, fallback to kernel cmdline, properties may not be ready yet
     if (fs_mgr_get_boot_config_from_kernel_cmdline(key, out_val)) {
         return true;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 42459ec..8ac3361 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -854,7 +854,7 @@
     if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
         std::set<std::string> boot_devices;
         const std::string cmdline_key = "androidboot.boot_device";
-        for (const auto& [key, value] : fs_mgr_parse_boot_config(cmdline)) {
+        for (const auto& [key, value] : fs_mgr_parse_cmdline(cmdline)) {
             if (key == cmdline_key) {
                 boot_devices.emplace(value);
             }
diff --git a/fs_mgr/fs_mgr_priv_boot_config.h b/fs_mgr/fs_mgr_priv_boot_config.h
index 417fb38..6a38401 100644
--- a/fs_mgr/fs_mgr_priv_boot_config.h
+++ b/fs_mgr/fs_mgr_priv_boot_config.h
@@ -22,11 +22,16 @@
 #include <utility>
 #include <vector>
 
-std::vector<std::pair<std::string, std::string>> fs_mgr_parse_boot_config(const std::string& cmdline);
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_cmdline(const std::string& cmdline);
 
 bool fs_mgr_get_boot_config_from_kernel(const std::string& cmdline, const std::string& key,
                                         std::string* out_val);
 bool fs_mgr_get_boot_config_from_kernel_cmdline(const std::string& key, std::string* out_val);
 bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val);
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_proc_bootconfig(
+        const std::string& bootconfig);
+bool fs_mgr_get_boot_config_from_bootconfig(const std::string& bootconfig, const std::string& key,
+                                            std::string* out_val);
+bool fs_mgr_get_boot_config_from_bootconfig_source(const std::string& key, std::string* out_val);
 
 #endif /* __CORE_FS_MGR_PRIV_BOOTCONFIG_H */
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 46f1c59..62a8d3b 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -119,10 +119,73 @@
         {"terminator", "truncated"},
 };
 
+const std::string bootconfig =
+        "androidboot.bootdevice  = \" \"1d84000.ufshc\"\n"
+        "androidboot.baseband = \"sdy\"\n"
+        "androidboot.keymaster = \"1\"\n"
+        "androidboot.serialno = \"BLAHBLAHBLAH\"\n"
+        "androidboot.slot_suffix = \"_a\"\n"
+        "androidboot.hardware.platform = \"sdw813\"\n"
+        "androidboot.hardware = \"foo\"\n"
+        "androidboot.revision = \"EVT1.0\"\n"
+        "androidboot.bootloader = \"burp-0.1-7521\"\n"
+        "androidboot.hardware.sku = \"mary\"\n"
+        "androidboot.hardware.radio.subtype = \"0\"\n"
+        "androidboot.dtbo_idx = \"2\"\n"
+        "androidboot.mode = \"normal\"\n"
+        "androidboot.hardware.ddr = \"1GB,combuchi,LPDDR4X\"\n"
+        "androidboot.ddr_info = \"combuchiandroidboot.ddr_size=2GB\"\n"
+        "androidboot.hardware.ufs = \"2GB,combushi\"\n"
+        "androidboot.boottime = \"0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123\"\n"
+        "androidboot.ramdump = \"disabled\"\n"
+        "androidboot.vbmeta.device = \"PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb\"\n"
+        "androidboot.vbmeta.avb_version = \"1.1\"\n"
+        "androidboot.vbmeta.device_state = \"unlocked\"\n"
+        "androidboot.vbmeta.hash_alg = \"sha256\"\n"
+        "androidboot.vbmeta.size = \"5248\"\n"
+        "androidboot.vbmeta.digest = \""
+        "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860\"\n"
+        "androidboot.vbmeta.invalidate_on_error = \"yes\"\n"
+        "androidboot.veritymode = \"enforcing\"\n"
+        "androidboot.verifiedbootstate = \"orange\"\n"
+        "androidboot.space = \"sha256 5248 androidboot.nospace = nope\"\n";
+
+const std::vector<std::pair<std::string, std::string>> bootconfig_result_space = {
+        {"androidboot.bootdevice", "1d84000.ufshc"},
+        {"androidboot.baseband", "sdy"},
+        {"androidboot.keymaster", "1"},
+        {"androidboot.serialno", "BLAHBLAHBLAH"},
+        {"androidboot.slot_suffix", "_a"},
+        {"androidboot.hardware.platform", "sdw813"},
+        {"androidboot.hardware", "foo"},
+        {"androidboot.revision", "EVT1.0"},
+        {"androidboot.bootloader", "burp-0.1-7521"},
+        {"androidboot.hardware.sku", "mary"},
+        {"androidboot.hardware.radio.subtype", "0"},
+        {"androidboot.dtbo_idx", "2"},
+        {"androidboot.mode", "normal"},
+        {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"},
+        {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"},
+        {"androidboot.hardware.ufs", "2GB,combushi"},
+        {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"},
+        {"androidboot.ramdump", "disabled"},
+        {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"},
+        {"androidboot.vbmeta.avb_version", "1.1"},
+        {"androidboot.vbmeta.device_state", "unlocked"},
+        {"androidboot.vbmeta.hash_alg", "sha256"},
+        {"androidboot.vbmeta.size", "5248"},
+        {"androidboot.vbmeta.digest",
+         "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"},
+        {"androidboot.vbmeta.invalidate_on_error", "yes"},
+        {"androidboot.veritymode", "enforcing"},
+        {"androidboot.verifiedbootstate", "orange"},
+        {"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
+};
+
 }  // namespace
 
-TEST(fs_mgr, fs_mgr_parse_boot_config) {
-    EXPECT_EQ(result_space, fs_mgr_parse_boot_config(cmdline));
+TEST(fs_mgr, fs_mgr_parse_cmdline) {
+    EXPECT_EQ(result_space, fs_mgr_parse_cmdline(cmdline));
 }
 
 TEST(fs_mgr, fs_mgr_get_boot_config_from_kernel_cmdline) {
@@ -140,6 +203,27 @@
     EXPECT_TRUE(content.empty()) << content;
 }
 
+TEST(fs_mgr, fs_mgr_parse_bootconfig) {
+    EXPECT_EQ(bootconfig_result_space, fs_mgr_parse_proc_bootconfig(bootconfig));
+}
+
+TEST(fs_mgr, fs_mgr_get_boot_config_from_bootconfig) {
+    std::string content;
+    for (const auto& entry : bootconfig_result_space) {
+        static constexpr char androidboot[] = "androidboot.";
+        if (!android::base::StartsWith(entry.first, androidboot)) continue;
+        auto key = entry.first.substr(strlen(androidboot));
+        EXPECT_TRUE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, key, &content))
+                << " for " << key;
+        EXPECT_EQ(entry.second, content);
+    }
+
+    EXPECT_FALSE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, "vbmeta.avb_versio", &content));
+    EXPECT_TRUE(content.empty()) << content;
+    EXPECT_FALSE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, "nospace", &content));
+    EXPECT_TRUE(content.empty()) << content;
+}
+
 TEST(fs_mgr, fs_mgr_read_fstab_file_proc_mounts) {
     Fstab fstab;
     ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab));
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 83d2b6d..551cf19 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -209,6 +209,8 @@
     CHECKCALL(chmod("/proc/cmdline", 0440));
     std::string cmdline;
     android::base::ReadFileToString("/proc/cmdline", &cmdline);
+    // Don't expose the raw bootconfig to unprivileged processes.
+    chmod("/proc/bootconfig", 0440);
     gid_t groups[] = {AID_READPROC};
     CHECKCALL(setgroups(arraysize(groups), groups));
     CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
diff --git a/init/property_service.cpp b/init/property_service.cpp
index ce67386..b722702 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1180,6 +1180,14 @@
     }
 }
 
+static void ProcessBootconfig() {
+    ImportBootconfig([&](const std::string& key, const std::string& value) {
+        if (StartsWith(key, "androidboot.")) {
+            InitPropertySet("ro.boot." + key.substr(12), value);
+        }
+    });
+}
+
 void PropertyInit() {
     selinux_callback cb;
     cb.func_audit = PropertyAuditCallback;
@@ -1198,6 +1206,7 @@
     // properties set in DT always have priority over the command-line ones.
     ProcessKernelDt();
     ProcessKernelCmdline();
+    ProcessBootconfig();
 
     // Propagate the kernel variables to internal variables
     // used by init as well as the current required properties.
diff --git a/init/util.cpp b/init/util.cpp
index 255434a..e69b43f 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -246,6 +246,19 @@
     }
 }
 
+void ImportBootconfig(const std::function<void(const std::string&, const std::string&)>& fn) {
+    std::string bootconfig;
+    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
+
+    for (const auto& entry : android::base::Split(bootconfig, "\n")) {
+        std::vector<std::string> pieces = android::base::Split(entry, "=");
+        if (pieces.size() == 2) {
+            pieces[1].erase(std::remove(pieces[1].begin(), pieces[1].end(), '"'), pieces[1].end());
+            fn(android::base::Trim(pieces[0]), android::base::Trim(pieces[1]));
+        }
+    }
+}
+
 bool make_dir(const std::string& path, mode_t mode) {
     std::string secontext;
     if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) {
diff --git a/init/util.h b/init/util.h
index 3cdc9f4..7745d77 100644
--- a/init/util.h
+++ b/init/util.h
@@ -55,6 +55,7 @@
 bool mkdir_recursive(const std::string& pathname, mode_t mode);
 int wait_for_file(const char *filename, std::chrono::nanoseconds timeout);
 void ImportKernelCmdline(const std::function<void(const std::string&, const std::string&)>&);
+void ImportBootconfig(const std::function<void(const std::string&, const std::string&)>&);
 bool make_dir(const std::string& path, mode_t mode);
 bool is_dir(const char* pathname);
 Result<std::string> ExpandProps(const std::string& src);