Drop inheritable caps and caps bounding set before executing payload

This change basically does the following things:

* Add rust_bindgen for the libcap.
* Add libcap_rust wrapping the bindgen and providing
  drop_inhertiable_caps and drop_bounding_set APIs;
* Call the libcap_rust APIs before execve'ing into the payload binary.
  This is done using the CommandExt::pre_exec function.

Additionally this change adds basic tests for libcap_rust library and
the e2e test to verify that binary running payload have zero
capabilities.

Bug: 243633980
Test: atest libcap_rust.test
Test: atest MicrodroidTestApp
Test: adb shell /apex/com.android.virt/bin/vm run-microdroid
Test: enter microdroid shell & check microdroid_launcher has empty caps
Change-Id: Ibfb45ec912df0ad0a1db62b24c22fbe5a61ff5f3
diff --git a/libs/capabilities/Android.bp b/libs/capabilities/Android.bp
new file mode 100644
index 0000000..db3f4d4
--- /dev/null
+++ b/libs/capabilities/Android.bp
@@ -0,0 +1,62 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+    name: "libcap_bindgen",
+    edition: "2021",
+    wrapper_src: "bindgen/libcap.h",
+    crate_name: "cap_bindgen",
+    source_stem: "bindings",
+    shared_libs: ["libcap"],
+    bindgen_flags: [
+        "--default-enum-style rust",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization:__subpackages__",
+    ],
+}
+
+rust_test {
+    name: "libcap_bindgen_test",
+    srcs: [":libcap_bindgen"],
+    crate_name: "cap_bindgen_test",
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    clippy_lints: "none",
+    lints: "none",
+}
+
+rust_defaults {
+    name: "libcap_rust.defaults",
+    crate_name: "cap",
+    srcs: ["src/caps.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libcap_bindgen",
+        "liblibc",
+        "libnix",
+        "libscopeguard",
+    ],
+    edition: "2021",
+    prefer_rlib: true,
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+    shared_libs: [
+        "libcap",
+    ],
+}
+
+rust_library {
+    name: "libcap_rust",
+    defaults: ["libcap_rust.defaults"],
+}
+
+rust_test {
+    name: "libcap_rust.test",
+    defaults: ["libcap_rust.defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/capabilities/bindgen/libcap.h b/libs/capabilities/bindgen/libcap.h
new file mode 100644
index 0000000..cbb90dc
--- /dev/null
+++ b/libs/capabilities/bindgen/libcap.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include <sys/capability.h>
diff --git a/libs/capabilities/src/caps.rs b/libs/capabilities/src/caps.rs
new file mode 100644
index 0000000..1f44a34
--- /dev/null
+++ b/libs/capabilities/src/caps.rs
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+//! A rust library wrapping the libcap functionality.
+
+use anyhow::{bail, Result};
+use cap_bindgen::{
+    cap_clear_flag, cap_drop_bound, cap_flag_t, cap_free, cap_get_proc, cap_set_proc, cap_value_t,
+    CAP_LAST_CAP,
+};
+use nix::errno::Errno;
+
+/// Removes inheritable capabilities set for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_inheritable_caps() -> Result<()> {
+    unsafe {
+        // SAFETY: we do not manipulate memory handled by libcap.
+        let caps = cap_get_proc();
+        scopeguard::defer! {
+            cap_free(caps as *mut std::os::raw::c_void);
+        }
+        if cap_clear_flag(caps, cap_flag_t::CAP_INHERITABLE) < 0 {
+            let e = Errno::last();
+            bail!("cap_clear_flag failed: {:?}", e)
+        }
+        if cap_set_proc(caps) < 0 {
+            let e = Errno::last();
+            bail!("cap_set_proc failed: {:?}", e)
+        }
+    }
+    Ok(())
+}
+
+/// Drop bounding set capabitilies for this process.
+/// See: https://man7.org/linux/man-pages/man7/capabilities.7.html
+pub fn drop_bounding_set() -> Result<()> {
+    let mut cap_id: cap_value_t = 0;
+    while cap_id <= CAP_LAST_CAP.try_into().unwrap() {
+        unsafe {
+            // SAFETY: we do not manipulate memory handled by libcap.
+            if cap_drop_bound(cap_id) == -1 {
+                let e = Errno::last();
+                bail!("cap_drop_bound failed for {}: {:?}", cap_id, e);
+            }
+        }
+        cap_id += 1;
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // Basic test to verify that calling drop_inheritable_caps doesn't fail
+    #[test]
+    fn test_drop_inheritable_caps() {
+        let result = drop_inheritable_caps();
+        assert!(result.is_ok(), "failed with: {:?}", result)
+    }
+}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 44b4c01..383f371 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -18,6 +18,7 @@
         "libapkverify",
         "libbinder_rs",
         "libbyteorder",
+        "libcap_rust",
         "libdiced",
         "libdiced_open_dice_cbor",
         "libdiced_sample_inputs",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index c41ee38..97d14b5 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -7,6 +7,8 @@
     setenv RUST_LOG info
     # TODO(jooyung) remove this when microdroid_manager becomes a daemon
     oneshot
-    # SYS_BOOT is required to exec kexecload from microdroid_manager
-    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT
+    # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
+    # CAP_SETCAP is required to allow microdroid_manager to drop capabilities
+    #   before executing the payload
+    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP
     socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 6a37b88..b081aac 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -55,6 +55,7 @@
 use std::env;
 use std::fs::{self, create_dir, OpenOptions};
 use std::io::Write;
+use std::os::unix::process::CommandExt;
 use std::os::unix::process::ExitStatusExt;
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
@@ -795,6 +796,24 @@
             command
         }
     };
+
+    unsafe {
+        // SAFETY: we are not accessing any resource of the parent process.
+        command.pre_exec(|| {
+            info!("dropping capabilities before executing payload");
+            // It is OK to continue with payload execution even if the calls below fail, since
+            // whether process can use a capability is controlled by the SELinux. Dropping the
+            // capabilities here is just another defense-in-depth layer.
+            if let Err(e) = cap::drop_inheritable_caps() {
+                error!("failed to drop inheritable capabilities: {:?}", e);
+            }
+            if let Err(e) = cap::drop_bounding_set() {
+                error!("failed to drop bounding set: {:?}", e);
+            }
+            Ok(())
+        });
+    }
+
     command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
 
     info!("notifying payload started");
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index a4ecc45..c936e1b 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -46,4 +46,7 @@
      * each line reverse.
      */
     void runEchoReverseServer();
+
+    /** Returns a mask of effective capabilities that the process running the payload binary has. */
+    String[] getEffectiveCapabilities();
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index edb4759..e3c9961 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -40,6 +40,7 @@
     header_libs: ["vm_payload_restricted_headers"],
     shared_libs: [
         "libbinder_ndk",
+        "libcap",
         "MicrodroidTestNativeLibSub",
         "libvm_payload#current",
     ],
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index eb756d8..f3bbbf1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1117,6 +1117,25 @@
         assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
     }
 
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void microdroidLauncherHasEmptyCapabilities() throws Exception {
+        assumeSupportedKernel();
+
+        final VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_caps", vmConfig);
+
+        final TestResults testResults = runVmTestService(vm);
+
+        assertThat(testResults.mException).isNull();
+        assertThat(testResults.mEffectiveCapabilities).isEmpty();
+    }
+
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
             throws IOException {
         File file1 = getVmFile(vmName1, fileName);
@@ -1171,6 +1190,7 @@
         String mExtraApkTestProp;
         String mApkContentsPath;
         String mEncryptedStoragePath;
+        String[] mEffectiveCapabilities;
     }
 
     private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -1194,6 +1214,8 @@
                             testResults.mApkContentsPath = testService.getApkContentsPath();
                             testResults.mEncryptedStoragePath =
                                     testService.getEncryptedStoragePath();
+                            testResults.mEffectiveCapabilities =
+                                    testService.getEffectiveCapabilities();
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index b6a7aa2..da408e4 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -18,12 +18,14 @@
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/result.h>
+#include <android-base/scopeguard.h>
 #include <android/log.h>
 #include <fcntl.h>
 #include <fsverity_digests.pb.h>
 #include <linux/vm_sockets.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <sys/capability.h>
 #include <sys/system_properties.h>
 #include <unistd.h>
 #include <vm_main.h>
@@ -35,6 +37,7 @@
 using android::base::borrowed_fd;
 using android::base::ErrnoError;
 using android::base::Error;
+using android::base::make_scope_guard;
 using android::base::Result;
 using android::base::unique_fd;
 
@@ -197,6 +200,29 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getEffectiveCapabilities(std::vector<std::string>* out) override {
+            if (out == nullptr) {
+                return ScopedAStatus::ok();
+            }
+            cap_t cap = cap_get_proc();
+            auto guard = make_scope_guard([&cap]() { cap_free(cap); });
+            for (cap_value_t cap_id = 0; cap_id < CAP_LAST_CAP + 1; cap_id++) {
+                cap_flag_value_t value;
+                if (cap_get_flag(cap, cap_id, CAP_EFFECTIVE, &value) != 0) {
+                    return ScopedAStatus::
+                            fromServiceSpecificErrorWithMessage(0, "cap_get_flag failed");
+                }
+                if (value == CAP_SET) {
+                    // Ideally we would just send back the cap_ids, but I wasn't able to find java
+                    // APIs for linux capabilities, hence we transform to the human readable name
+                    // here.
+                    char* name = cap_to_name(cap_id);
+                    out->push_back(std::string(name) + "(" + std::to_string(cap_id) + ")");
+                }
+            }
+            return ScopedAStatus::ok();
+        }
+
         virtual ::ScopedAStatus runEchoReverseServer() override {
             auto result = start_echo_reverse_server();
             if (result.ok()) {