More accurately validate the prerequisites for using emmc_optimized

The emmc_optimized encryption setting is allowed only when the inline
crypto hardware supports only 32-bit DUNs; see CDD 9.9.3.1 [C-1-15].
vold tries to validate this setting accordingly, but previously it did
not have access to the actual DUN length, so instead it checked the
storage type to determine whether a 32-bit DUN limit *might* exist.  In
android14-5.15 and later kernels, the DUN length is exposed via a file
in the disk's queue directory in sysfs, which allows doing this check
properly.  Therefore, update vold to use this file when possible.

Bug: 207390665
Test: Set emmc_optimized on (a) a device using UFS storage, and (b) a
      device using virtio storage with no inline crypto support passed
      through.  Both with PRODUCT_SHIPPING_API_LEVEL = 35.  Verified
      that the devices failed to boot and the log showed the expected
      reasons.  (I did not have a device actually using eMMC at hand.)
Change-Id: Ib2f1cab313aa57e8d5aabe4ebfcc2d08909fcc89
diff --git a/FsCrypt.cpp b/FsCrypt.cpp
index 3eb4599..4f42dd8 100644
--- a/FsCrypt.cpp
+++ b/FsCrypt.cpp
@@ -37,6 +37,7 @@
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/utsname.h>
 #include <unistd.h>
 
 #include <private/android_filesystem_config.h>
@@ -233,7 +234,20 @@
     return false;
 }
 
-static bool MightBeEmmcStorage(const std::string& blk_device) {
+// Checks whether the kernel definitely supports the sysfs files that describe the storage
+// hardware's inline encryption capabilities.  They are supported in upstream 5.18 and later, and in
+// android14-5.15 and later (but not android13-5.15).  For simplicity we just check for 5.18.
+static bool DoesKernelSupportBlkCryptoSysfsFiles() {
+    struct utsname uts;
+    unsigned int major = 0, minor = 0;
+    if (uname(&uts) != 0 || sscanf(uts.release, "%u.%u", &major, &minor) != 2) {
+        return true;  // This should never happen; assume new rather than old.
+    }
+    return major > 5 || (major == 5 && minor >= 18);
+}
+
+// Checks whether the storage hardware might support only 32-bit data unit numbers.
+static bool DoesHardwareSupportOnly32DunBits(const std::string& blk_device) {
     // Handle symlinks.
     std::string real_path;
     if (!Realpath(blk_device, &real_path)) {
@@ -249,15 +263,53 @@
     }
 
     // Now we should have the "real" block device.
-    LOG(DEBUG) << "MightBeEmmcStorage(): blk_device = " << blk_device
-               << ", real_path=" << real_path;
     std::string name = Basename(real_path);
-    return StartsWith(name, "mmcblk") ||
-           // virtio devices may provide inline encryption support that is
-           // backed by eMMC inline encryption on the host, thus inheriting the
-           // DUN size limitation.  So virtio devices must be allowed here too.
-           // TODO(b/207390665): check the maximum DUN size directly instead.
-           StartsWith(name, "vd");
+
+    // If possible, do the check precisely via sysfs.
+    // Exclude older devices, just in case they are broken by doing the check correctly...
+    if (GetFirstApiLevel() >= __ANDROID_API_V__) {
+        std::string sysfs_path = "/sys/class/block/" + name + "/queue/crypto/max_dun_bits";
+        if (!android::vold::pathExists(sysfs_path)) {
+            // For a partition, "queue" is in the parent directory which represents the disk.
+            sysfs_path = "/sys/class/block/" + name + "/../queue/crypto/max_dun_bits";
+        }
+        if (android::vold::pathExists(sysfs_path)) {
+            std::string max_dun_bits;
+            if (!android::base::ReadFileToString(sysfs_path, &max_dun_bits)) {
+                PLOG(ERROR) << "Error reading " << sysfs_path;
+                return false;
+            }
+            max_dun_bits = android::base::Trim(max_dun_bits);
+            if (max_dun_bits != "32") {
+                LOG(ERROR) << sysfs_path << " = " << max_dun_bits;
+                // In this case, using emmc_optimized is not appropriate because the hardware
+                // supports inline encryption but does not have the 32-bit DUN limit.
+                return false;
+            }
+            LOG(DEBUG) << sysfs_path << " = " << max_dun_bits;
+            return true;
+        }
+        if (DoesKernelSupportBlkCryptoSysfsFiles()) {
+            // In this case, using emmc_optimized is not appropriate because the hardware does not
+            // support inline encryption.
+            LOG(ERROR) << sysfs_path << " does not exist";
+            return false;
+        }
+        // In this case, the kernel might be too old to support the sysfs files.
+    }
+
+    // Fallback method for older kernels that don't have the crypto capabilities in sysfs.  The
+    // 32-bit DUN limit is only known to exist on eMMC storage, and also on virtio storage that
+    // inherits the limit from eMMC on the host.  So allow either of those storage types.  Note that
+    // this can be overly lenient compared to actually checking max_dun_bits.
+    if (StartsWith(name, "mmcblk") || StartsWith(name, "vd")) {
+        LOG(DEBUG) << __func__ << "(): << blk_device = " << blk_device
+                   << ", real_path = " << real_path;
+        return true;
+    }
+    // Log at ERROR level here so that it shows up in the kernel log.
+    LOG(ERROR) << __func__ << "(): << blk_device = " << blk_device << ", real_path = " << real_path;
+    return false;
 }
 
 // Sets s_data_options to the file encryption options for the /data filesystem.
@@ -273,9 +325,10 @@
         return false;
     }
     if ((s_data_options.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) &&
-        !MightBeEmmcStorage(entry->blk_device)) {
-        LOG(ERROR) << "The emmc_optimized encryption flag is only allowed on eMMC storage.  Remove "
-                      "this flag from the device's fstab";
+        !DoesHardwareSupportOnly32DunBits(entry->blk_device)) {
+        // This would unnecessarily reduce security and not be compliant with the CDD.
+        LOG(ERROR) << "The emmc_optimized encryption flag is only allowed on hardware limited to "
+                      "32-bit DUNs.  Remove this flag from the device's fstab";
         return false;
     }
     return true;