blob: bfc210fc9d43b743d3c0f9431b130a786fe9456a [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.
//! Common utilities for pseudo file implementations
use {
fidl_fuchsia_io::{
OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE, OPEN_FLAG_APPEND, OPEN_FLAG_CREATE,
OPEN_FLAG_CREATE_IF_ABSENT, 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_FLAG_TRUNCATE,
OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_zircon as zx,
libc::{S_IRUSR, S_IWUSR},
};
/// POSIX emulation layer access attributes for all files created with read_only().
pub const POSIX_READ_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IRUSR;
/// POSIX emulation layer access attributes for all files created with write_only().
pub const POSIX_WRITE_ONLY_PROTECTION_ATTRIBUTES: u32 = S_IWUSR;
/// POSIX emulation layer access attributes for all files created with read_write().
pub const POSIX_READ_WRITE_PROTECTION_ATTRIBUTES: u32 =
POSIX_READ_ONLY_PROTECTION_ATTRIBUTES | POSIX_WRITE_ONLY_PROTECTION_ATTRIBUTES;
/// Validate that the requested flags for a new connection are valid. This function will make sure
/// that `flags` only requests read access when `readable` is true, or only write access when
/// `writable` is true, or either when both are true. It also does some sanity checking and
/// normalization, matching `flags` with the `mode` value.
///
/// On success, returns the validated flags, with some ambiguities cleaned up. On failure, it
/// returns a [`Status`] indicating the problem.
///
/// `OPEN_FLAG_NODE_REFERENCE` is preserved and prohibits both read and write access.
///
/// Changing this function can be dangerous! Flags operations may have security implications.
pub fn new_connection_validate_flags(
mut flags: u32,
mut readable: bool,
mut writable: bool,
append_allowed: bool,
) -> Result<u32, zx::Status> {
if flags & OPEN_FLAG_NODE_REFERENCE != 0 {
flags &= OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE;
readable = false;
writable = false;
}
if flags & OPEN_FLAG_DIRECTORY != 0 {
return Err(zx::Status::NOT_DIR);
}
if flags & OPEN_FLAG_NOT_DIRECTORY != 0 {
flags &= !OPEN_FLAG_NOT_DIRECTORY;
}
// For files 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);
let allowed_flags = OPEN_FLAG_NODE_REFERENCE
| OPEN_FLAG_DESCRIBE
| OPEN_FLAG_CREATE
| OPEN_FLAG_CREATE_IF_ABSENT
| if readable { OPEN_RIGHT_READABLE } else { 0 }
| if writable { OPEN_RIGHT_WRITABLE | OPEN_FLAG_TRUNCATE } else { 0 }
| if writable && append_allowed { OPEN_FLAG_APPEND } else { 0 };
let prohibited_flags = (0 | if readable {
OPEN_FLAG_TRUNCATE
} else {
0
} | if writable {
OPEN_FLAG_APPEND
} else {
0
})
// allowed_flags takes precedence over prohibited_flags.
& !allowed_flags
// TRUNCATE is only allowed if the file is writable.
| if flags & OPEN_RIGHT_WRITABLE == 0 {
OPEN_FLAG_TRUNCATE
} else {
0
};
if !readable && flags & OPEN_RIGHT_READABLE != 0 {
return Err(zx::Status::ACCESS_DENIED);
}
if !writable && flags & OPEN_RIGHT_WRITABLE != 0 {
return Err(zx::Status::ACCESS_DENIED);
}
if flags & prohibited_flags != 0 {
return Err(zx::Status::INVALID_ARGS);
}
if flags & !allowed_flags != 0 {
return Err(zx::Status::NOT_SUPPORTED);
}
Ok(flags)
}
#[cfg(test)]
mod tests {
use super::new_connection_validate_flags;
use crate::test_utils::build_flag_combinations;
use {
fidl_fuchsia_io::{
OPEN_FLAG_APPEND, OPEN_FLAG_CREATE, OPEN_FLAG_CREATE_IF_ABSENT, OPEN_FLAG_DESCRIBE,
OPEN_FLAG_DIRECTORY, OPEN_FLAG_NODE_REFERENCE, OPEN_FLAG_NOT_DIRECTORY,
OPEN_FLAG_POSIX, OPEN_FLAG_TRUNCATE, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_zircon as zx,
};
#[track_caller]
fn ncvf_ok(flags: u32, readable: bool, writable: bool, expected_new_flags: u32) {
let res = new_connection_validate_flags(
flags, readable, writable, /*append_allowed=*/ false,
);
match res {
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),
}
}
#[track_caller]
fn ncvf_err(flags: u32, readable: bool, writable: bool, expected_status: zx::Status) {
let res = new_connection_validate_flags(
flags, readable, writable, /*append_allowed=*/ false,
);
match res {
Ok(new_flags) => panic!(
"new_connection_validate_flags should have failed. \
Got new flags: {:X}",
new_flags
),
Err(status) => assert_eq!(expected_status, status),
}
}
#[test]
fn new_connection_validate_flags_node_reference() {
// Should drop access flags but preserve OPEN_FLAG_NODE_REFERENCE and OPEN_FLAG_DESCRIBE.
let preserved_flags = OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DESCRIBE;
for open_flags in build_flag_combinations(
OPEN_FLAG_NODE_REFERENCE,
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE | OPEN_FLAG_DESCRIBE,
) {
ncvf_ok(open_flags, true, true, open_flags & preserved_flags);
}
ncvf_err(OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_DIRECTORY, true, true, zx::Status::NOT_DIR);
ncvf_ok(
OPEN_FLAG_NODE_REFERENCE | OPEN_FLAG_NOT_DIRECTORY,
true,
true,
OPEN_FLAG_NODE_REFERENCE,
);
}
#[test]
fn new_connection_validate_flags_posix() {
// OPEN_FLAG_POSIX is ignored for files.
for open_flags in
build_flag_combinations(OPEN_FLAG_POSIX, OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE)
{
let readable = open_flags & OPEN_RIGHT_READABLE != 0;
let writable = open_flags & OPEN_RIGHT_WRITABLE != 0;
ncvf_ok(open_flags, readable, writable, open_flags & !OPEN_FLAG_POSIX);
}
}
#[test]
fn new_connection_validate_flags_create() {
for open_flags in build_flag_combinations(
OPEN_FLAG_CREATE,
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE | OPEN_FLAG_CREATE_IF_ABSENT,
) {
let readable = open_flags & OPEN_RIGHT_READABLE != 0;
let writable = open_flags & OPEN_RIGHT_WRITABLE != 0;
ncvf_ok(open_flags, readable, writable, open_flags);
}
}
#[test]
fn new_connection_validate_flags_truncate() {
ncvf_err(OPEN_RIGHT_READABLE | OPEN_FLAG_TRUNCATE, true, true, zx::Status::INVALID_ARGS);
ncvf_ok(
OPEN_RIGHT_WRITABLE | OPEN_FLAG_TRUNCATE,
true,
true,
OPEN_RIGHT_WRITABLE | OPEN_FLAG_TRUNCATE,
);
ncvf_err(OPEN_RIGHT_READABLE | OPEN_FLAG_TRUNCATE, true, false, zx::Status::INVALID_ARGS);
}
#[test]
fn new_connection_validate_flags_append() {
ncvf_err(OPEN_RIGHT_READABLE | OPEN_FLAG_APPEND, true, false, zx::Status::NOT_SUPPORTED);
ncvf_err(OPEN_RIGHT_WRITABLE | OPEN_FLAG_APPEND, true, true, zx::Status::INVALID_ARGS);
}
#[test]
fn new_connection_validate_flags_open_rights() {
for open_flags in build_flag_combinations(0, OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE) {
let readable = open_flags & OPEN_RIGHT_READABLE != 0;
let writable = open_flags & OPEN_RIGHT_WRITABLE != 0;
ncvf_ok(open_flags, readable, writable, open_flags);
if readable {
ncvf_err(open_flags, false, writable, zx::Status::ACCESS_DENIED);
}
if writable {
ncvf_err(open_flags, readable, false, zx::Status::ACCESS_DENIED);
}
}
}
}