blob: e5e75db38648479bfc9c31dae42dbe5a6362926b [file] [log] [blame]
// Copyright 2019 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.
//! Code shared between several modules of the service implementation.
use {
fidl_fuchsia_io::{
MODE_PROTECTION_MASK, MODE_TYPE_DIRECTORY, MODE_TYPE_FILE, MODE_TYPE_MASK,
MODE_TYPE_SERVICE, OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE, OPEN_FLAG_DESCRIBE,
OPEN_FLAG_DIRECTORY, OPEN_FLAG_NODE_REFERENCE, OPEN_FLAG_NOT_DIRECTORY, OPEN_FLAG_POSIX,
OPEN_FLAG_POSIX_EXECUTABLE, OPEN_FLAG_POSIX_WRITABLE, OPEN_RIGHT_READABLE,
OPEN_RIGHT_WRITABLE,
},
fuchsia_zircon::Status,
libc::{S_IRUSR, S_IWUSR},
};
/// POSIX emulation layer access attributes for all services created with service().
pub const POSIX_READ_WRITE_PROTECTION_ATTRIBUTES: u32 = S_IRUSR | S_IWUSR;
/// Validate that the requested flags for a new connection are valid. It is a bit tricky as
/// depending on the presence of the `OPEN_FLAG_NODE_REFERENCE` flag we are effectively validating
/// two different cases: with `OPEN_FLAG_NODE_REFERENCE` the connection will be attached to the
/// service node itself, and without `OPEN_FLAG_NODE_REFERENCE` the connection will be forwarded to
/// the backing service.
///
/// `new_connection_validate_flags` will preserve `OPEN_FLAG_NODE_REFERENCE` to make it easier for
/// the caller to distinguish these two cases.
///
/// On success, returns the validated and cleaned flags. On failure, it returns a [`Status`]
/// indicating the problem.
///
/// Changing this function can be dangerous! Flags operations may have security implications.
pub fn new_connection_validate_flags(mut flags: u32, mode: u32) -> Result<u32, Status> {
// There should be no MODE_TYPE_* flags set, except for, possibly, MODE_TYPE_SOCKET when the
// target is a service.
if (mode & !MODE_PROTECTION_MASK) & !MODE_TYPE_SERVICE != 0 {
if mode & MODE_TYPE_MASK == MODE_TYPE_DIRECTORY {
return Err(Status::NOT_DIR);
} else if mode & MODE_TYPE_MASK == MODE_TYPE_FILE {
return Err(Status::NOT_FILE);
} else {
return Err(Status::INVALID_ARGS);
};
}
// A service is not a directory.
flags &= !OPEN_FLAG_NOT_DIRECTORY;
// For services the OPEN_FLAG_POSIX flags are ignored as they have meaning only for directories.
flags &= !(OPEN_FLAG_POSIX | OPEN_FLAG_POSIX_WRITABLE | OPEN_FLAG_POSIX_EXECUTABLE);
if flags & OPEN_FLAG_DIRECTORY != 0 {
return Err(Status::NOT_DIR);
}
if flags & OPEN_FLAG_NODE_REFERENCE != 0 {
flags &= !(OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE);
if flags & !OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE != 0 {
return Err(Status::INVALID_ARGS);
}
flags &= OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE;
return Ok(flags);
}
// All the flags we have already checked above and removed.
debug_assert!(
flags
& (OPEN_FLAG_DIRECTORY
| OPEN_FLAG_NOT_DIRECTORY
| OPEN_FLAG_POSIX
| OPEN_FLAG_POSIX_WRITABLE
| OPEN_FLAG_POSIX_EXECUTABLE
| OPEN_FLAG_NODE_REFERENCE)
== 0
);
// A service might only be connected to when both read and write permissions are present.
if flags & OPEN_RIGHT_READABLE == 0 || flags & OPEN_RIGHT_WRITABLE == 0 {
return Err(Status::ACCESS_DENIED);
}
let allowed_flags = OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE;
// OPEN_FLAG_DESCRIBE is not allowed when connecting directly to the service itself.
if flags & OPEN_FLAG_DESCRIBE != 0 {
return Err(Status::INVALID_ARGS);
}
// Anything else is also not allowed.
if flags & !allowed_flags != 0 {
return Err(Status::INVALID_ARGS);
}
Ok(flags)
}
#[cfg(test)]
mod tests {
use super::new_connection_validate_flags;
use {
fidl_fuchsia_io::{
MODE_TYPE_DIRECTORY, MODE_TYPE_FILE, MODE_TYPE_SERVICE, MODE_TYPE_SOCKET,
OPEN_FLAG_DESCRIBE, OPEN_FLAG_DIRECTORY, OPEN_FLAG_NODE_REFERENCE,
OPEN_FLAG_NOT_DIRECTORY, OPEN_FLAG_POSIX, OPEN_FLAG_POSIX_EXECUTABLE,
OPEN_FLAG_POSIX_WRITABLE, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_zircon::Status,
};
/// Assertion for when `new_connection_validate_flags` should succeed => `ncvf_ok`.
fn ncvf_ok(flags: u32, mode: u32, expected_new_flags: u32) {
match new_connection_validate_flags(flags, mode) {
Ok(new_flags) => assert_eq!(
expected_new_flags, new_flags,
"new_connection_validate_flags returned unexpected set of flags.\n\
Expected: {:X}\n\
Actual: {:X}",
expected_new_flags, new_flags
),
Err(status) => panic!("new_connection_validate_flags failed. Status: {}", status),
}
}
/// Assertion for when `new_connection_validate_flags` should fail => `ncvf_err`.
fn ncvf_err(flags: u32, mode: u32, expected_status: Status) {
match new_connection_validate_flags(flags, mode) {
Ok(new_flags) => panic!(
"new_connection_validate_flags should have failed.\n\
Got new flags: {:X}",
new_flags
),
Err(status) => assert_eq!(expected_status, status),
}
}
/// Common combination for the service tests.
const READ_WRITE: u32 = OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE;
#[test]
fn node_reference_basic() {
// OPEN_FLAG_NODE_REFERENCE is preserved.
ncvf_ok(OPEN_FLAG_NODE_REFERENCE, 0, OPEN_FLAG_NODE_REFERENCE);
// Access flags are dropped.
ncvf_ok(OPEN_FLAG_NODE_REFERENCE | OPEN_RIGHT_READABLE, 0, OPEN_FLAG_NODE_REFERENCE);
ncvf_ok(OPEN_FLAG_NODE_REFERENCE | OPEN_RIGHT_WRITABLE, 0, OPEN_FLAG_NODE_REFERENCE);
// OPEN_FLAG_DESCRIBE is preserved.
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE,
0,
OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE,
);
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE | READ_WRITE | OPEN_FLAG_DESCRIBE,
0,
OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE,
);
ncvf_ok(OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_NOT_DIRECTORY, 0, OPEN_FLAG_NODE_REFERENCE);
ncvf_err(OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DIRECTORY, 0, Status::NOT_DIR);
}
#[test]
fn service_basic() {
// Access flags are required and preserved.
ncvf_ok(READ_WRITE, 0, READ_WRITE);
ncvf_err(OPEN_RIGHT_READABLE, 0, Status::ACCESS_DENIED);
ncvf_err(OPEN_RIGHT_WRITABLE, 0, Status::ACCESS_DENIED);
// OPEN_FLAG_DESCRIBE is not allowed.
ncvf_err(READ_WRITE | OPEN_FLAG_DESCRIBE, 0, Status::INVALID_ARGS);
ncvf_ok(READ_WRITE | OPEN_FLAG_NOT_DIRECTORY, 0, READ_WRITE);
ncvf_err(READ_WRITE | OPEN_FLAG_DIRECTORY, 0, Status::NOT_DIR);
}
#[test]
fn node_reference_posix() {
// OPEN_FLAG_POSIX is ignored for services.
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE
| OPEN_FLAG_POSIX
| OPEN_FLAG_POSIX_WRITABLE
| OPEN_FLAG_POSIX_EXECUTABLE,
0,
OPEN_FLAG_NODE_REFERENCE,
);
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE
| OPEN_FLAG_DESCRIBE
| OPEN_FLAG_POSIX
| OPEN_FLAG_POSIX_WRITABLE
| OPEN_FLAG_POSIX_EXECUTABLE,
0,
OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE,
);
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE
| READ_WRITE
| OPEN_FLAG_POSIX
| OPEN_FLAG_POSIX_WRITABLE
| OPEN_FLAG_POSIX_EXECUTABLE,
0,
OPEN_FLAG_NODE_REFERENCE,
);
}
#[test]
fn service_posix() {
// OPEN_FLAG_POSIX is ignored for services.
ncvf_ok(
READ_WRITE | OPEN_FLAG_POSIX | OPEN_FLAG_POSIX_WRITABLE | OPEN_FLAG_POSIX_EXECUTABLE,
0,
READ_WRITE,
);
ncvf_err(
READ_WRITE
| OPEN_FLAG_DESCRIBE
| OPEN_FLAG_POSIX
| OPEN_FLAG_POSIX_WRITABLE
| OPEN_FLAG_POSIX_EXECUTABLE,
0,
Status::INVALID_ARGS,
);
}
#[test]
fn file() {
ncvf_err(OPEN_FLAG_NODE_REFERENCE, MODE_TYPE_FILE, Status::NOT_FILE);
ncvf_err(READ_WRITE, MODE_TYPE_FILE, Status::NOT_FILE);
}
#[test]
fn mode_directory() {
ncvf_err(OPEN_FLAG_NODE_REFERENCE, MODE_TYPE_DIRECTORY, Status::NOT_DIR);
ncvf_err(READ_WRITE, MODE_TYPE_DIRECTORY, Status::NOT_DIR);
}
#[test]
fn mode_socket() {
ncvf_err(OPEN_FLAG_NODE_REFERENCE, MODE_TYPE_SOCKET, Status::INVALID_ARGS);
ncvf_err(READ_WRITE, MODE_TYPE_SOCKET, Status::INVALID_ARGS);
}
#[test]
fn mode_service() {
ncvf_ok(OPEN_FLAG_NODE_REFERENCE, MODE_TYPE_SERVICE, OPEN_FLAG_NODE_REFERENCE);
ncvf_ok(READ_WRITE, MODE_TYPE_SERVICE, READ_WRITE);
}
}