| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| assert_matches::assert_matches, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_io as fio, |
| io_conformance_util::{test_harness::TestHarness, *}, |
| }; |
| |
| /// Creates a directory with a remote mount inside of it, and checks that the remote can be opened. |
| #[fuchsia::test] |
| async fn open_remote_directory_test() { |
| let harness = TestHarness::new().await; |
| if !harness.config.supports_remote_dir.unwrap_or_default() { |
| return; |
| } |
| |
| let remote_name = "remote_directory"; |
| let remote_mount = root_directory(vec![]); |
| let remote_client = harness.get_directory( |
| remote_mount, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, |
| ); |
| |
| // Create a directory with the remote directory inside of it. |
| let root = root_directory(vec![remote_directory(remote_name, remote_client)]); |
| let root_dir = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| open_node::<fio::DirectoryMarker>( |
| &root_dir, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DIRECTORY, |
| remote_name, |
| ) |
| .await; |
| } |
| |
| /// Creates a directory with a remote mount containing a file inside of it, and checks that the |
| /// file can be opened through the remote. |
| #[fuchsia::test] |
| async fn open_remote_file_test() { |
| let harness = TestHarness::new().await; |
| if !harness.config.supports_remote_dir.unwrap_or_default() { |
| return; |
| } |
| |
| let remote_name = "remote_directory"; |
| let remote_dir = root_directory(vec![file(TEST_FILE, vec![])]); |
| let remote_client = harness.get_directory(remote_dir, fio::OpenFlags::RIGHT_READABLE); |
| |
| // Create a directory with the remote directory inside of it. |
| let root = root_directory(vec![remote_directory(remote_name, remote_client)]); |
| let root_dir = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| // Test opening file by opening the remote directory first and then opening the file. |
| let remote_dir_proxy = open_node::<fio::DirectoryMarker>( |
| &root_dir, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DIRECTORY, |
| remote_name, |
| ) |
| .await; |
| open_node::<fio::NodeMarker>(&remote_dir_proxy, fio::OpenFlags::RIGHT_READABLE, TEST_FILE) |
| .await; |
| |
| // Test opening file directly though local directory by crossing remote automatically. |
| open_node::<fio::NodeMarker>( |
| &root_dir, |
| fio::OpenFlags::RIGHT_READABLE, |
| [remote_name, "/", TEST_FILE].join("").as_str(), |
| ) |
| .await; |
| } |
| |
| /// Ensure specifying POSIX_* flags cannot cause rights escalation (https://fxbug.dev/42116881). |
| /// The test sets up the following hierarchy of nodes: |
| /// |
| /// --------------------- RW -------------------------- |
| /// | root_proxy | ---> | root | |
| /// --------------------- (a) | - /mount_point | RWX --------------------- |
| /// | (remote_proxy) | ---> | remote_dir | |
| /// -------------------------- (b) --------------------- |
| /// |
| /// To validate the right escalation issue has been resolved, we call Open() on the test_dir_proxy |
| /// passing in both POSIX_* flags, which if handled correctly, should result in opening |
| /// remote_dir_server as RW (and NOT RWX, which can occur if both flags are passed directly to the |
| /// remote instead of being removed). |
| #[fuchsia::test] |
| async fn open_remote_directory_right_escalation_test() { |
| let harness = TestHarness::new().await; |
| if !harness.config.supports_remote_dir.unwrap_or_default() { |
| return; |
| } |
| |
| let mount_point = "mount_point"; |
| |
| // Use the test harness to serve a directory with RWX permissions. |
| let remote_dir = root_directory(vec![]); |
| let remote_proxy = harness.get_directory( |
| remote_dir, |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::RIGHT_EXECUTABLE, |
| ); |
| |
| // Mount the remote directory through root, and ensure that the connection only has RW |
| // RW permissions (which is thus a sub-set of the permissions the remote_proxy has). |
| let root = root_directory(vec![remote_directory(mount_point, remote_proxy)]); |
| let root_proxy = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| // Create a new proxy/server for opening the remote node through test_dir_proxy. |
| // Here we pass the POSIX flag, which should only expand to the maximum set of |
| // rights available along the open chain. |
| let (node_proxy, node_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy"); |
| root_proxy |
| .open( |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::POSIX_WRITABLE |
| | fio::OpenFlags::POSIX_EXECUTABLE |
| | fio::OpenFlags::DIRECTORY, |
| fio::ModeType::empty(), |
| mount_point, |
| node_server, |
| ) |
| .expect("Cannot open remote directory"); |
| |
| // Since the root node only has RW permissions, and even though the remote has RWX, |
| // we should only get RW permissions back. |
| let (_, node_flags) = node_proxy.get_flags().await.unwrap(); |
| assert_eq!(node_flags, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| } |
| |
| /// Creates a directory with a remote mount inside of it, and checks that the remote can be opened. |
| #[fuchsia::test] |
| async fn open2_remote_directory_test() { |
| let harness = TestHarness::new().await; |
| if !(harness.config.supports_remote_dir.unwrap_or_default() |
| && harness.config.supports_open2.unwrap_or_default()) |
| { |
| return; |
| } |
| let remote_name = "remote_directory"; |
| let remote_mount = root_directory(vec![]); |
| let remote_client = harness.get_directory( |
| remote_mount, |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE, |
| ); |
| |
| // Create a directory with the remote directory inside of it. |
| let root = root_directory(vec![remote_directory(remote_name, remote_client)]); |
| let root_dir = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| root_dir |
| .open2_node::<fio::DirectoryMarker>( |
| remote_name, |
| fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(Default::default()), |
| ..Default::default() |
| }), |
| rights: Some(fio::Operations::READ_BYTES | fio::Operations::WRITE_BYTES), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("failed to open remote directory"); |
| } |
| |
| /// Creates a directory with a remote mount containing a file inside of it, and checks that the |
| /// file can be opened through the remote. |
| #[fuchsia::test] |
| async fn open2_remote_file_test() { |
| let harness = TestHarness::new().await; |
| if !(harness.config.supports_remote_dir.unwrap_or_default() |
| && harness.config.supports_open2.unwrap_or_default()) |
| { |
| return; |
| } |
| |
| let remote_name = "remote_directory"; |
| let remote_dir = root_directory(vec![file(TEST_FILE, vec![])]); |
| let remote_client = harness.get_directory(remote_dir, fio::OpenFlags::RIGHT_READABLE); |
| |
| // Create a directory with the remote directory inside of it. |
| let root = root_directory(vec![remote_directory(remote_name, remote_client)]); |
| let root_dir = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| // Test opening file by opening the remote directory first and then opening the file. |
| let remote_dir_proxy = root_dir |
| .open2_node::<fio::DirectoryMarker>( |
| remote_name, |
| fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(Default::default()), |
| ..Default::default() |
| }), |
| rights: Some(fio::Operations::READ_BYTES), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("failed to open remote directory"); |
| |
| let file_options = fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| file: Some(Default::default()), |
| ..Default::default() |
| }), |
| rights: Some(fio::Operations::READ_BYTES), |
| ..Default::default() |
| }; |
| |
| remote_dir_proxy |
| .open2_node::<fio::NodeMarker>(TEST_FILE, file_options.clone()) |
| .await |
| .expect("failed to open file in remote directory"); |
| |
| // Test opening file directly though local directory by crossing remote automatically. |
| root_dir |
| .open2_node::<fio::NodeMarker>( |
| [remote_name, "/", TEST_FILE].join("").as_str(), |
| file_options, |
| ) |
| .await |
| .expect("failed to open file when traversing a remote mount point"); |
| } |
| |
| /// Ensure specifying optional rights cannot cause rights escalation. The test sets up the following |
| /// hierarchy of nodes: |
| /// |
| /// --------------------- RW -------------------------- |
| /// | root_proxy | ---> | root | |
| /// --------------------- (a) | - /mount_point | RWX --------------------- |
| /// | (remote_proxy) | ---> | remote_dir | |
| /// -------------------------- (b) --------------------- |
| /// |
| /// It then verifies that opening `remote_dir` through `root_proxy` will remove any specified |
| /// optional rights not present during any intermediate opening steps. |
| #[fuchsia::test] |
| async fn open2_remote_directory_right_escalation_test() { |
| let harness = TestHarness::new().await; |
| if !(harness.config.supports_remote_dir.unwrap_or_default() |
| && harness.config.supports_open2.unwrap_or_default()) |
| { |
| return; |
| } |
| |
| let mount_point = "mount_point"; |
| |
| // Use the test harness to serve a directory with RWX permissions. |
| let remote_dir = root_directory(vec![]); |
| let remote_proxy = harness.get_directory( |
| remote_dir, |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::RIGHT_EXECUTABLE, |
| ); |
| |
| // Mount the remote directory through root, and ensure that the connection only has RW |
| // RW permissions (which is thus a sub-set of the permissions the remote_proxy has). |
| let root = root_directory(vec![remote_directory(mount_point, remote_proxy)]); |
| let root_proxy = harness |
| .get_directory(root, fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE); |
| |
| // Open the remote with read rights as required, but write/execute as optional. |
| let options = fio::NodeOptions { |
| protocols: Some(fio::NodeProtocols { |
| directory: Some(fio::DirectoryProtocolOptions { |
| optional_rights: Some(fio::Rights::WRITE_BYTES | fio::Rights::EXECUTE), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }), |
| rights: Some(fio::Rights::READ_BYTES), |
| ..Default::default() |
| }; |
| let proxy = root_proxy |
| .open2_node::<fio::NodeMarker>(mount_point, options) |
| .await |
| .expect("failed to open remote node"); |
| |
| // Ensure the resulting connection expanded write but not execute rights. |
| let connection_info = proxy.get_connection_info().await.unwrap(); |
| assert_matches!(connection_info, fio::ConnectionInfo{ rights: Some(rights), .. } => { |
| assert!(!rights.contains(fio::Operations::EXECUTE)); |
| assert!(rights.intersects(fio::Operations::READ_BYTES | fio::Operations::WRITE_BYTES))}); |
| } |