Fix OOM in createOrUpdateIdsigFile when a directory is passed

createOrUpdateIdsigFile uses V4Signature::create function, which takes
an fd that is passed to it and lseeks to the end of it. If that fd
corresponds to a directory on ext4, then lseek will return (off_t)-1. As
a result, the function will OOM trying to allocate 72 petabytes of
memory.

This change fixes the issue by adding verification logic in
createOrUpdateIdsigFile that passed pfd corresponds to a regular file.
It also adds unit tests for createOrUpdateIdsigFile.

I've also added comment to V4Signature::create telling callers that they
should validate their fd before calling the function.

Bug: 261840405
Test: atest virtualizationservice_device_test
Test: adb shell /apex/com.android.virt/bin/vm create-idsig \
        /apex/com.android.virt/app/EmptyPayloadApp@AOSP.MASTER \
        /data/local/tmp/fun-with-microdroid
Change-Id: Iddb694e10946eefb4df8959d06fe3488e9c6ac66
diff --git a/libs/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index 6c085f6..94abf99 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -146,6 +146,11 @@
 
     /// Read a stream for an APK file and creates a corresponding `V4Signature` struct that digests
     /// the APK file. Note that the signing is not done.
+    /// Important: callers of this function are expected to verify the validity of the passed |apk|.
+    /// To be more specific, they should check that |apk| corresponds to a regular file, as calling
+    /// lseek on directory fds is not defined in the standard, and on ext4 it will return (off_t)-1
+    /// (see: https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in this
+    /// function OOMing.
     pub fn create(
         mut apk: &mut R,
         block_size: usize,
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 3e4323d..edca883 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -115,6 +115,24 @@
     }
 }
 
+fn create_or_update_idsig_file(
+    input_fd: &ParcelFileDescriptor,
+    idsig_fd: &ParcelFileDescriptor,
+) -> Result<()> {
+    let mut input = clone_file(input_fd)?;
+    let metadata = input.metadata().context("failed to get input metadata")?;
+    if !metadata.is_file() {
+        bail!("input is not a regular file");
+    }
+    let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256)
+        .context("failed to create idsig")?;
+
+    let mut output = clone_file(idsig_fd)?;
+    output.set_len(0).unwrap();
+    sig.write_into(&mut output).unwrap();
+    Ok(())
+}
+
 /// Singleton service for allocating globally-unique VM resources, such as the CID, and running
 /// singleton servers, like tombstone receiver.
 #[derive(Debug, Default)]
@@ -345,12 +363,8 @@
 
         check_manage_access()?;
 
-        let mut input = clone_file(input_fd)?;
-        let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
-
-        let mut output = clone_file(idsig_fd)?;
-        output.set_len(0).unwrap();
-        sig.write_into(&mut output).unwrap();
+        create_or_update_idsig_file(input_fd, idsig_fd)
+            .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
         Ok(())
     }
 
@@ -1303,4 +1317,50 @@
         }
         Ok(())
     }
+
+    #[test]
+    fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
+        let apk = tempfile::tempfile().unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
+
+    #[test]
+    fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
+        let tmp_dir = tempfile::TempDir::new().unwrap();
+        let apk = File::open(tmp_dir.path()).unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
+
+    /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
+    /// on ext4 filesystem is passed.
+    /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
+    /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
+    /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
+    #[test]
+    fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
+        // APEXes are backed by the ext4.
+        let apk = File::open("/apex/com.android.virt/").unwrap();
+        let idsig = tempfile::tempfile().unwrap();
+
+        let ret = create_or_update_idsig_file(
+            &ParcelFileDescriptor::new(apk),
+            &ParcelFileDescriptor::new(idsig),
+        );
+        assert!(ret.is_err(), "should fail");
+        Ok(())
+    }
 }