[kernel] Require valid rsrc to debuglog_create if readable

Bug: 32044
Fixed: 94740
Change-Id: Iedf2368c07e9d27c9f8930220a575edf34e21532
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/653202
Reviewed-by: Marco Vanotti <mvanotti@google.com>
Fuchsia-Auto-Submit: Drew Fisher <zarvox@google.com>
Reviewed-by: Abdulla Kamar <abdulla@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/src/lib/zircon/rust/src/debuglog.rs b/src/lib/zircon/rust/src/debuglog.rs
index 3a33ca3..9e92221 100644
--- a/src/lib/zircon/rust/src/debuglog.rs
+++ b/src/lib/zircon/rust/src/debuglog.rs
@@ -89,12 +89,23 @@
 mod tests {
     use super::*;
     use crate::{cprng_draw, Signals, Time};
+    use fidl_fuchsia_boot as fboot;
+    use fuchsia_component::client::connect_channel_to_protocol;
 
     // expect_message_in_debuglog will read the last 10000 messages in zircon's debuglog, looking
     // for a message that equals `sent_msg`. If found, the function returns. If the first 10,000
     // messages doesn't contain `sent_msg`, it will panic.
     fn expect_message_in_debuglog(sent_msg: String) {
-        let resource = Resource::from(Handle::invalid());
+        use fuchsia_zircon::{Channel, HandleBased};
+        let (client_end, server_end) = Channel::create().unwrap();
+        connect_channel_to_protocol::<fboot::RootResourceMarker>(server_end).unwrap();
+        let service = fboot::RootResourceSynchronousProxy::new(client_end);
+        let resource =
+            service.get(fuchsia_zircon::Time::INFINITE).expect("couldn't get root resource");
+        // This test and fuchsia-zircon are different crates, so we need
+        // to use from_raw to convert between the fuchsia_zircon handle and this test handle.
+        // See https://fxbug.dev/91562 for details.
+        let resource = unsafe { Resource::from(Handle::from_raw(resource.into_raw())) };
         let debuglog = DebugLog::create(&resource, DebugLogOpts::READABLE).unwrap();
         for _ in 0..10000 {
             match debuglog.read() {
diff --git a/src/sys/component_manager/lib/logger/BUILD.gn b/src/sys/component_manager/lib/logger/BUILD.gn
index d4e3e99..d1c176a2 100644
--- a/src/sys/component_manager/lib/logger/BUILD.gn
+++ b/src/sys/component_manager/lib/logger/BUILD.gn
@@ -28,7 +28,11 @@
     "//third_party/rust_crates:log",
   ]
 
-  test_deps = [ "//third_party/rust_crates:rand" ]
+  test_deps = [
+    "//sdk/fidl/fuchsia.boot:fuchsia.boot-rustc",
+    "//src/lib/fuchsia-component",
+    "//third_party/rust_crates:rand",
+  ]
 
   sources = [
     "src/fmt.rs",
@@ -39,5 +43,6 @@
 }
 
 fuchsia_unittest_package("component_manager_logger_test") {
+  manifest = "meta/component_manager_logger_test.cml"
   deps = [ ":logger_test" ]
 }
diff --git a/src/sys/component_manager/lib/logger/meta/component_manager_logger_test.cml b/src/sys/component_manager/lib/logger/meta/component_manager_logger_test.cml
new file mode 100644
index 0000000..5eea9d0
--- /dev/null
+++ b/src/sys/component_manager/lib/logger/meta/component_manager_logger_test.cml
@@ -0,0 +1,13 @@
+{
+    include: [
+        "//src/sys/test_manager/system-test.shard.cml",
+        "//src/sys/test_runners/rust/default.shard.cml",
+        "syslog/client.shard.cml",
+    ],
+    program: {
+        binary: "bin/cm_logger_lib_test",
+    },
+    use: [
+        { protocol: "fuchsia.boot.RootResource" },
+    ],
+}
diff --git a/src/sys/component_manager/lib/logger/src/klog.rs b/src/sys/component_manager/lib/logger/src/klog.rs
index 1640cb9..750f2bf 100644
--- a/src/sys/component_manager/lib/logger/src/klog.rs
+++ b/src/sys/component_manager/lib/logger/src/klog.rs
@@ -109,17 +109,27 @@
     use {
         super::*,
         anyhow::Context,
+        fidl_fuchsia_boot as fboot,
+        fuchsia_component::client::connect_channel_to_protocol,
         fuchsia_zircon::{AsHandleRef, HandleBased},
         log::*,
         rand::Rng,
         std::panic,
     };
 
+    fn get_root_resource() -> zx::Resource {
+        let (client_end, server_end) = zx::Channel::create().unwrap();
+        connect_channel_to_protocol::<fboot::RootResourceMarker>(server_end).unwrap();
+        let service = fboot::RootResourceSynchronousProxy::new(client_end);
+        let resource = service.get(zx::Time::INFINITE).expect("couldn't get root resource");
+        resource
+    }
+
     // expect_message_in_debuglog will read the last 10000 messages in zircon's debuglog, looking
     // for a message that equals `sent_msg`. If found, the function returns. If the first 10,000
     // messages doesn't contain `sent_msg`, it will panic.
     fn expect_message_in_debuglog(sent_msg: String) {
-        let resource = zx::Resource::from(zx::Handle::invalid());
+        let resource = get_root_resource();
         let debuglog = zx::DebugLog::create(&resource, zx::DebugLogOpts::READABLE).unwrap();
         for _ in 0..10000 {
             match debuglog.read() {
diff --git a/src/sys/component_manager/src/builtin/log.rs b/src/sys/component_manager/src/builtin/log.rs
index 73acd3c..0644fcf 100644
--- a/src/sys/component_manager/src/builtin/log.rs
+++ b/src/sys/component_manager/src/builtin/log.rs
@@ -106,6 +106,7 @@
         cm_task_scope::TaskScope,
         fidl::endpoints::ClientEnd,
         fuchsia_async as fasync,
+        fuchsia_component::client::connect_to_protocol,
         fuchsia_zircon::sys,
         fuchsia_zircon::AsHandleRef,
         futures::lock::Mutex,
@@ -114,11 +115,28 @@
         std::sync::Weak,
     };
 
+    fn root_resource_available() -> bool {
+        let bin = std::env::args().next();
+        match bin.as_ref().map(String::as_ref) {
+            Some("/pkg/bin/component_manager_test") => false,
+            Some("/pkg/bin/component_manager_boot_env_test") => true,
+            _ => panic!("Unexpected test binary name {:?}", bin),
+        }
+    }
+
+    async fn get_root_resource() -> Result<zx::Resource, Error> {
+        let root_resource_provider = connect_to_protocol::<fboot::RootResourceMarker>()?;
+        let root_resource_handle = root_resource_provider.get().await?;
+        Ok(zx::Resource::from(root_resource_handle))
+    }
+
     #[fuchsia::test]
     async fn has_correct_rights_for_read_only() -> Result<(), Error> {
-        // The kernel does not currently require a valid `Resource` to be
-        // provided when creating a `Debuglog`. This will change in the future.
-        let read_only_log = ReadOnlyLog::new(Resource::from(zx::Handle::invalid()));
+        if !root_resource_available() {
+            return Ok(());
+        }
+        let resource = get_root_resource().await?;
+        let read_only_log = ReadOnlyLog::new(resource);
         let (proxy, stream) =
             fidl::endpoints::create_proxy_and_stream::<fboot::ReadOnlyLogMarker>()?;
         fasync::Task::local(
@@ -136,9 +154,11 @@
 
     #[fuchsia::test]
     async fn can_connect_to_read_only() -> Result<(), Error> {
-        // The kernel does not currently require a valid `Resource` to be
-        // provided when creating a `Debuglog`. This will change in the future.
-        let read_only_log = ReadOnlyLog::new(Resource::from(zx::Handle::invalid()));
+        if !root_resource_available() {
+            return Ok(());
+        }
+        let resource = get_root_resource().await?;
+        let read_only_log = ReadOnlyLog::new(resource);
         let hooks = Hooks::new(None);
         hooks.install(read_only_log.hooks()).await;
 
@@ -176,8 +196,8 @@
 
     #[fuchsia::test]
     async fn has_correct_rights_for_write_only() -> Result<(), Error> {
-        // The kernel does not currently require a valid `Resource` to be
-        // provided when creating a `Debuglog`. This will change in the future.
+        // The kernel requires a valid `Resource` to be provided when creating a `Debuglog` that
+        // can be read from, but not one that can be written to.  This may change in the future.
         let resource = Resource::from(zx::Handle::invalid());
         let write_only_log =
             WriteOnlyLog::new(zx::DebugLog::create(&resource, zx::DebugLogOpts::empty()).unwrap());
@@ -199,8 +219,8 @@
 
     #[fuchsia::test]
     async fn can_connect_to_write_only() -> Result<(), Error> {
-        // The kernel does not currently require a valid `Resource` to be
-        // provided when creating a `Debuglog`. This will change in the future.
+        // The kernel requires a valid `Resource` to be provided when creating a `Debuglog` that
+        // can be read from, but not one that can be written to.  This may change in the future.
         let resource = Resource::from(zx::Handle::invalid());
         let write_only_log =
             WriteOnlyLog::new(zx::DebugLog::create(&resource, zx::DebugLogOpts::empty()).unwrap());
diff --git a/zircon/kernel/lib/syscalls/zircon.cc b/zircon/kernel/lib/syscalls/zircon.cc
index 6142451..1d9b6d3 100644
--- a/zircon/kernel/lib/syscalls/zircon.cc
+++ b/zircon/kernel/lib/syscalls/zircon.cc
@@ -125,8 +125,13 @@
 zx_status_t sys_debuglog_create(zx_handle_t rsrc, uint32_t options, user_out_handle* out) {
   LTRACEF("options 0x%x\n", options);
 
-  // TODO(fxbug.dev/32044) Require a non-INVALID handle.
-  if (rsrc != ZX_HANDLE_INVALID) {
+  // To support allowing the libc dynamic linker to emit log messages even
+  // before process bootstrap is complete, we allow creating a debuglog with
+  // options == 0 (write-only) without yet having a valid `rsrc` handle.
+  // Otherwise, we should require a valid `rsrc` handle.
+  // Inversely: if a resource handle is given, or if `options` is nonzero,
+  // require that `rsrc` be a valid root resource handle.
+  if (rsrc != ZX_HANDLE_INVALID || options != 0) {
     // TODO(fxbug.dev/30918): finer grained validation
     zx_status_t status = validate_resource(rsrc, ZX_RSRC_KIND_ROOT);
     if (status != ZX_OK)
diff --git a/zircon/system/utest/core/debuglog/debuglog.cc b/zircon/system/utest/core/debuglog/debuglog.cc
index 2517213..1fc073f 100644
--- a/zircon/system/utest/core/debuglog/debuglog.cc
+++ b/zircon/system/utest/core/debuglog/debuglog.cc
@@ -49,6 +49,28 @@
   EXPECT_EQ(log_handle, 0);
 }
 
+TEST(DebugLogTest, InvalidHandleAllowedOnlyForWrite) {
+  zx_handle_t log_handle = 0;
+
+  // Requesting a read-write handle with a valid root resource should work
+  ASSERT_OK(zx_debuglog_create(get_root_resource(), ZX_LOG_FLAG_READABLE, &log_handle));
+  ASSERT_OK(zx_handle_close(log_handle));
+  log_handle = ZX_HANDLE_INVALID;
+
+  // Requesting a write-only handle with an invalid root resource should work,
+  // since the dynamic linker needs to be able to log something somewhere when
+  // it can't bootstrap a process
+  ASSERT_OK(zx_debuglog_create(ZX_HANDLE_INVALID, 0, &log_handle));
+  ASSERT_OK(zx_handle_close(log_handle));
+  log_handle = ZX_HANDLE_INVALID;
+
+  // But requesting a read-write handle with an invalid root resource represents
+  // an information leak, so the kernel checks the first arg, and if it's not a
+  // valid handle, then we get an error
+  EXPECT_EQ(zx_debuglog_create(ZX_HANDLE_INVALID, ZX_LOG_FLAG_READABLE, &log_handle),
+            ZX_ERR_BAD_HANDLE);
+}
+
 TEST(DebugLogTest, MaxMessageSize) {
   zx_handle_t log_handle = ZX_HANDLE_INVALID;
   ASSERT_OK(zx_debuglog_create(get_root_resource(), ZX_LOG_FLAG_READABLE, &log_handle));