Add libfstab_fuzzer that fuzzes ReadFstabFile()

Just a modest initial implementation. Uses the fuzz data as the fstab
file content directly.

Bug: 204056804
Test: lunch aosp_cf_x86_64_phone-userdebug
  SANITIZE_TARGET=address m libfstab_fuzzer
  adb sync data
  adb shell /data/fuzz/x86_64/libfstab_fuzzer/libfstab_fuzzer
Change-Id: I7976a6ee124e9b5da59cfa7f4bae9699be3f1474
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9b6c3dd..ad321f0 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -442,7 +442,81 @@
     return "";
 }
 
-bool ReadFstabFile(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
+/* Extracts <device>s from the by-name symlinks specified in a fstab:
+ *   /dev/block/<type>/<device>/by-name/<partition>
+ *
+ * <type> can be: platform, pci or vbd.
+ *
+ * For example, given the following entries in the input fstab:
+ *   /dev/block/platform/soc/1da4000.ufshc/by-name/system
+ *   /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor
+ * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }.
+ */
+std::set<std::string> ExtraBootDevices(const Fstab& fstab) {
+    std::set<std::string> boot_devices;
+
+    for (const auto& entry : fstab) {
+        std::string blk_device = entry.blk_device;
+        // Skips blk_device that doesn't conform to the format.
+        if (!android::base::StartsWith(blk_device, "/dev/block") ||
+            android::base::StartsWith(blk_device, "/dev/block/by-name") ||
+            android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) {
+            continue;
+        }
+        // Skips non-by_name blk_device.
+        // /dev/block/<type>/<device>/by-name/<partition>
+        //                           ^ slash_by_name
+        auto slash_by_name = blk_device.find("/by-name");
+        if (slash_by_name == std::string::npos) continue;
+        blk_device.erase(slash_by_name);  // erases /by-name/<partition>
+
+        // Erases /dev/block/, now we have <type>/<device>
+        blk_device.erase(0, std::string("/dev/block/").size());
+
+        // <type>/<device>
+        //       ^ first_slash
+        auto first_slash = blk_device.find('/');
+        if (first_slash == std::string::npos) continue;
+
+        auto boot_device = blk_device.substr(first_slash + 1);
+        if (!boot_device.empty()) boot_devices.insert(std::move(boot_device));
+    }
+
+    return boot_devices;
+}
+
+FstabEntry BuildDsuUserdataFstabEntry() {
+    constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV;
+
+    FstabEntry userdata = {
+            .blk_device = "userdata_gsi",
+            .mount_point = "/data",
+            .fs_type = "ext4",
+            .flags = kFlags,
+            .reserved_size = 128 * 1024 * 1024,
+    };
+    userdata.fs_mgr_flags.wait = true;
+    userdata.fs_mgr_flags.check = true;
+    userdata.fs_mgr_flags.logical = true;
+    userdata.fs_mgr_flags.quota = true;
+    userdata.fs_mgr_flags.late_mount = true;
+    userdata.fs_mgr_flags.formattable = true;
+    return userdata;
+}
+
+bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
+    auto iter = std::remove_if(fstab->begin(), fstab->end(),
+                               [&](const auto& entry) { return entry.mount_point == mount_point; });
+    if (iter != fstab->end()) {
+        fstab->erase(iter, fstab->end());
+        return true;
+    }
+    return false;
+}
+
+}  // namespace
+
+bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out) {
     ssize_t len;
     size_t alloc_len = 0;
     char *line = NULL;
@@ -528,80 +602,6 @@
     return false;
 }
 
-/* Extracts <device>s from the by-name symlinks specified in a fstab:
- *   /dev/block/<type>/<device>/by-name/<partition>
- *
- * <type> can be: platform, pci or vbd.
- *
- * For example, given the following entries in the input fstab:
- *   /dev/block/platform/soc/1da4000.ufshc/by-name/system
- *   /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor
- * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }.
- */
-std::set<std::string> ExtraBootDevices(const Fstab& fstab) {
-    std::set<std::string> boot_devices;
-
-    for (const auto& entry : fstab) {
-        std::string blk_device = entry.blk_device;
-        // Skips blk_device that doesn't conform to the format.
-        if (!android::base::StartsWith(blk_device, "/dev/block") ||
-            android::base::StartsWith(blk_device, "/dev/block/by-name") ||
-            android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) {
-            continue;
-        }
-        // Skips non-by_name blk_device.
-        // /dev/block/<type>/<device>/by-name/<partition>
-        //                           ^ slash_by_name
-        auto slash_by_name = blk_device.find("/by-name");
-        if (slash_by_name == std::string::npos) continue;
-        blk_device.erase(slash_by_name);  // erases /by-name/<partition>
-
-        // Erases /dev/block/, now we have <type>/<device>
-        blk_device.erase(0, std::string("/dev/block/").size());
-
-        // <type>/<device>
-        //       ^ first_slash
-        auto first_slash = blk_device.find('/');
-        if (first_slash == std::string::npos) continue;
-
-        auto boot_device = blk_device.substr(first_slash + 1);
-        if (!boot_device.empty()) boot_devices.insert(std::move(boot_device));
-    }
-
-    return boot_devices;
-}
-
-FstabEntry BuildDsuUserdataFstabEntry() {
-    constexpr uint32_t kFlags = MS_NOATIME | MS_NOSUID | MS_NODEV;
-
-    FstabEntry userdata = {
-            .blk_device = "userdata_gsi",
-            .mount_point = "/data",
-            .fs_type = "ext4",
-            .flags = kFlags,
-            .reserved_size = 128 * 1024 * 1024,
-    };
-    userdata.fs_mgr_flags.wait = true;
-    userdata.fs_mgr_flags.check = true;
-    userdata.fs_mgr_flags.logical = true;
-    userdata.fs_mgr_flags.quota = true;
-    userdata.fs_mgr_flags.late_mount = true;
-    userdata.fs_mgr_flags.formattable = true;
-    return userdata;
-}
-
-bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
-    auto iter = std::remove_if(fstab->begin(), fstab->end(),
-                               [&](const auto& entry) { return entry.mount_point == mount_point; });
-    if (iter != fstab->end()) {
-        fstab->erase(iter, fstab->end());
-        return true;
-    }
-    return false;
-}
-
-}  // namespace
-
 void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot,
                           const std::vector<std::string>& dsu_partitions) {
     static constexpr char kDsuKeysDir[] = "/avb";
@@ -707,7 +707,7 @@
     bool is_proc_mounts = path == "/proc/mounts";
 
     Fstab fstab;
-    if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, &fstab)) {
+    if (!ReadFstabFromFp(fstab_file.get(), is_proc_mounts, &fstab)) {
         LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
         return false;
     }
@@ -762,7 +762,7 @@
         return false;
     }
 
-    if (!ReadFstabFile(fstab_file.get(), false, fstab)) {
+    if (!ReadFstabFromFp(fstab_file.get(), false, fstab)) {
         if (verbose) {
             LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
                    << fstab_buf;
diff --git a/fs_mgr/fuzz/Android.bp b/fs_mgr/fuzz/Android.bp
new file mode 100644
index 0000000..f86d277
--- /dev/null
+++ b/fs_mgr/fuzz/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2021 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.
+//
+
+cc_fuzz {
+  name: "libfstab_fuzzer",
+  srcs: [
+    "fs_mgr_fstab_fuzzer.cpp",
+  ],
+  static_libs: [
+    "libfstab",
+  ],
+  shared_libs: [
+    "libbase",
+  ],
+}
diff --git a/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
new file mode 100644
index 0000000..1fddbf8
--- /dev/null
+++ b/fs_mgr/fuzz/fs_mgr_fstab_fuzzer.cpp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2021 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 <cstdio>
+
+#include <fstab/fstab.h>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    std::unique_ptr<FILE, decltype(&fclose)> fstab_file(
+            fmemopen(static_cast<void*>(const_cast<uint8_t*>(data)), size, "r"), fclose);
+    if (fstab_file == nullptr) {
+        return 0;
+    }
+    android::fs_mgr::Fstab fstab;
+    android::fs_mgr::ReadFstabFromFp(fstab_file.get(), /* proc_mounts= */ false, &fstab);
+    return 0;
+}
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 9a4ed46..d0f32a3 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -99,6 +99,9 @@
 // Unless explicitly requested, a lookup on mount point should always return the 1st one.
 using Fstab = std::vector<FstabEntry>;
 
+// Exported for testability. Regular users should use ReadFstabFromfile().
+bool ReadFstabFromFp(FILE* fstab_file, bool proc_mounts, Fstab* fstab_out);
+
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
 bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
 bool ReadDefaultFstab(Fstab* fstab);