Skip to content

Commit c2a9f58

Browse files
drwezCQ Bot
authored andcommitted
[sestarnix] Initial file_permission() hook and integration
Implement the LSM file_permission(), hook, which validates that the current task still has the specified set of read/write/execute permissions to an open FileObject. Bug: 364569337, 385121365 Change-Id: Idae66deada0a59b5023236264eb9aa08d63d958f Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1111716 Reviewed-by: Nelly Vouzoukidou <[email protected]> Fuchsia-Auto-Submit: Wez <[email protected]> Commit-Queue: Auto-Submit <[email protected]>
1 parent a48b6fb commit c2a9f58

File tree

5 files changed

+89
-72
lines changed

5 files changed

+89
-72
lines changed

src/starnix/kernel/security/hooks.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ pub struct FsNodeSecurityXattr {
178178
pub value: FsString,
179179
}
180180

181+
/// Checks whether the `current_task` has the specified `permission_flags` to the `file`.
182+
/// Corresponds to the `file_permission()` LSM hook.
183+
pub fn file_permission(
184+
current_task: &CurrentTask,
185+
file: &FileObject,
186+
permission_flags: PermissionFlags,
187+
) -> Result<(), Errno> {
188+
if_selinux_else_default_ok(current_task, |security_server| {
189+
selinux_hooks::file_permission(security_server, current_task, file, permission_flags)
190+
})
191+
}
192+
181193
/// Called by the VFS to initialize the security state for an `FsNode` that is being linked at
182194
/// `dir_entry`.
183195
/// If the `FsNode` security state had already been initialized, or no policy is yet loaded, then

src/starnix/kernel/security/selinux_hooks/mod.rs

Lines changed: 62 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::vfs::{
1414
DirEntry, DirEntryHandle, FileHandle, FileObject, FileSystem, FileSystemHandle, FsNode, FsStr,
1515
FsString, PathBuilder, UnlinkKind, ValueOrSize, XattrOp,
1616
};
17+
use crate::TODO_DENY;
1718
use audit::{audit_log, AuditContext};
1819
use bstr::BStr;
1920
use linux_uapi::XATTR_NAME_SELINUX;
@@ -31,6 +32,7 @@ use starnix_uapi::arc_key::WeakKey;
3132
use starnix_uapi::device_type::DeviceType;
3233
use starnix_uapi::errors::{Errno, ENODATA};
3334
use starnix_uapi::file_mode::FileMode;
35+
use starnix_uapi::open_flags::OpenFlags;
3436
use starnix_uapi::{
3537
errno, error, FIGETBSZ, FIOASYNC, FIONBIO, FIONREAD, FS_IOC_GETFLAGS, FS_IOC_GETVERSION,
3638
FS_IOC_SETFLAGS, FS_IOC_SETVERSION,
@@ -42,6 +44,29 @@ use std::sync::{Arc, OnceLock};
4244
/// contexts in a filesystem node extended attributes.
4345
const SECURITY_SELINUX_XATTR_VALUE_MAX_SIZE: usize = 4096;
4446

47+
/// Returns the set of `Permissions` on `class`, corresponding to the specified `flags`.
48+
fn permissions_from_flags(flags: PermissionFlags, class: FileClass) -> Vec<Permission> {
49+
let mut result = Vec::new();
50+
if flags.contains(PermissionFlags::READ) {
51+
result.push(CommonFilePermission::Read.for_class(class));
52+
}
53+
// SELinux uses the `APPEND` bit to distinguish which of the "append" or the more general
54+
// "write" permission to check for.
55+
if flags.contains(PermissionFlags::APPEND) {
56+
result.push(CommonFilePermission::Append.for_class(class));
57+
} else if flags.contains(PermissionFlags::WRITE) {
58+
result.push(CommonFilePermission::Write.for_class(class));
59+
}
60+
if flags.contains(PermissionFlags::EXEC) {
61+
if class == FileClass::Dir {
62+
result.push(DirPermission::Search.into());
63+
} else {
64+
result.push(CommonFilePermission::Execute.for_class(class));
65+
}
66+
}
67+
result
68+
}
69+
4570
/// Checks that `current_task` has permission to "use" the specified `file`, and the specified
4671
/// `permissions` to the underlying [`crate::vfs::FsNode`].
4772
fn has_file_permissions(
@@ -53,7 +78,8 @@ fn has_file_permissions(
5378
// Validate that the `subject` has the "fd { use }" permission to the `file`.
5479
// If the file and task security domains are identical then `fd { use }` is implicitly granted.
5580
let file_sid = file.security_state.state.sid;
56-
if subject_sid != file_sid {
81+
// TODO: https://fxbug.dev/385121365 - Should the kernel be implicitly allowed to "use" all FDs?
82+
if subject_sid != SecurityId::initial(InitialSid::Kernel) && subject_sid != file_sid {
5783
check_permission(permission_check, subject_sid, file_sid, FdPermission::Use)?;
5884
}
5985

@@ -108,6 +134,31 @@ fn todo_has_fs_node_permissions(
108134
Ok(())
109135
}
110136

137+
/// Checks whether the `current_task`` has the permissions specified by `mask` to the `file`.
138+
pub fn file_permission(
139+
security_server: &SecurityServer,
140+
current_task: &CurrentTask,
141+
file: &FileObject,
142+
mut permission_flags: PermissionFlags,
143+
) -> Result<(), Errno> {
144+
let current_sid = current_task.security_state.lock().current_sid;
145+
let file_mode = file.name.entry.node.info().mode;
146+
let file_class = file_class_from_file_mode(file_mode)?;
147+
148+
if file.flags().contains(OpenFlags::APPEND) {
149+
permission_flags |= PermissionFlags::APPEND;
150+
}
151+
152+
has_file_permissions(&security_server.as_permission_check(), current_sid, file, &[])?;
153+
todo_has_fs_node_permissions(
154+
TODO_DENY!("https://fxbug.dev/385121365", "Enforce file_permission() checks"),
155+
&security_server.as_permission_check(),
156+
current_sid,
157+
file.node(),
158+
&permissions_from_flags(permission_flags, file_class),
159+
)
160+
}
161+
111162
/// Returns the relative path from the root of the file system containing this `DirEntry`.
112163
fn get_fs_relative_path(dir_entry: &DirEntryHandle) -> FsString {
113164
let mut path_builder = PathBuilder::new();
@@ -300,7 +351,8 @@ fn make_fs_node_security_xattr(
300351
}
301352

302353
fn file_class_from_file_mode(mode: FileMode) -> Result<FileClass, Errno> {
303-
match mode.bits() & starnix_uapi::S_IFMT {
354+
let file_type = mode.bits() & starnix_uapi::S_IFMT;
355+
match file_type {
304356
starnix_uapi::S_IFLNK => Ok(FileClass::Link),
305357
starnix_uapi::S_IFREG => Ok(FileClass::File),
306358
starnix_uapi::S_IFDIR => Ok(FileClass::Dir),
@@ -779,70 +831,14 @@ pub fn fs_node_permission(
779831
permission_flags: PermissionFlags,
780832
) -> Result<(), Errno> {
781833
let current_sid = current_task.security_state.lock().current_sid;
782-
let FsNodeSidAndClass { sid: file_sid, class: file_class } =
783-
fs_node_effective_sid_and_class(fs_node);
784-
if permission_flags.contains(PermissionFlags::READ) {
785-
todo_check_permission(
786-
TODO_DENY!(
787-
"https://fxbug.dev/380855359",
788-
"Check read permission when calling fs_node_permission."
789-
),
790-
&security_server.as_permission_check(),
791-
current_sid,
792-
file_sid,
793-
CommonFilePermission::Read.for_class(file_class),
794-
)?;
795-
}
796-
797-
if permission_flags.contains(PermissionFlags::WRITE) {
798-
todo_check_permission(
799-
TODO_DENY!(
800-
"https://fxbug.dev/380855359",
801-
"Check write permission when calling fs_node_permission."
802-
),
803-
&security_server.as_permission_check(),
804-
current_sid,
805-
file_sid,
806-
CommonFilePermission::Write.for_class(file_class),
807-
)?;
808-
}
809-
810-
if permission_flags.contains(PermissionFlags::APPEND) {
811-
check_permission(
812-
&security_server.as_permission_check(),
813-
current_sid,
814-
file_sid,
815-
CommonFilePermission::Append.for_class(file_class),
816-
)?;
817-
}
818-
819-
if permission_flags.contains(PermissionFlags::EXEC) {
820-
if file_class == FileClass::Dir {
821-
todo_check_permission(
822-
TODO_DENY!(
823-
"https://fxbug.dev/380855359",
824-
"Check search permission when calling fs_node_permission."
825-
),
826-
&security_server.as_permission_check(),
827-
current_sid,
828-
file_sid,
829-
DirPermission::Search,
830-
)?;
831-
} else {
832-
todo_check_permission(
833-
TODO_DENY!(
834-
"https://fxbug.dev/380855359",
835-
"Check execute permission when calling fs_node_permission."
836-
),
837-
&security_server.as_permission_check(),
838-
current_sid,
839-
file_sid,
840-
CommonFilePermission::Execute.for_class(file_class),
841-
)?;
842-
}
843-
}
844-
845-
Ok(())
834+
let file_class = fs_node.security_state.lock().class;
835+
todo_has_fs_node_permissions(
836+
TODO_DENY!("https://fxbug.dev/380855359", "Enforce fs_node_permission checks."),
837+
&security_server.as_permission_check(),
838+
current_sid,
839+
fs_node,
840+
&permissions_from_flags(permission_flags, file_class),
841+
)
846842
}
847843

848844
pub(super) fn check_fs_node_getattr_access(

src/starnix/kernel/security/selinux_hooks/task.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
use crate::security::selinux_hooks::{
66
check_permission, check_self_permission, fs_node_effective_sid_and_class, fs_node_ensure_class,
7-
fs_node_set_label_with_task, has_file_permissions, todo_check_permission, FsNode,
8-
PermissionCheck, ProcessPermission, TaskAttrs,
7+
fs_node_set_label_with_task, has_file_permissions, todo_check_permission, PermissionCheck,
8+
ProcessPermission, TaskAttrs,
99
};
1010
use crate::security::{Arc, ProcAttr, ResolvedElfState, SecurityId, SecurityServer};
1111
use crate::task::{CurrentTask, Task};
12+
use crate::vfs::FsNode;
1213
use crate::TODO_DENY;
1314
use selinux::{FilePermission, NullessByteStr, ObjectClass};
1415
use starnix_types::ownership::TempRef;

src/starnix/kernel/vfs/file_object.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::vfs::{
1919
FdNumber, FdTableId, FileReleaser, FileSystemHandle, FileWriteGuard, FileWriteGuardMode,
2020
FileWriteGuardRef, FsNodeHandle, NamespaceNode, RecordLockCommand, RecordLockOwner,
2121
};
22+
2223
use fidl::HandleBased;
2324
use fidl_fuchsia_fxfs::CryptManagementMarker;
2425
use fuchsia_inspect_contrib::profile_duration;
@@ -201,6 +202,7 @@ pub trait FileOps: Send + Sync + AsAny + 'static {
201202
offset: usize,
202203
data: &mut dyn OutputBuffer,
203204
) -> Result<usize, Errno>;
205+
204206
/// Write to the file with an offset. If the file does not have persistent offsets (either
205207
/// directly, or because it is not seekable), offset will be 0 and can be ignored.
206208
/// Returns the number of bytes written.
@@ -1657,10 +1659,12 @@ impl FileObject {
16571659
}
16581660

16591661
/// Common implementation for `read` and `read_at`.
1660-
fn read_internal<R>(&self, read: R) -> Result<usize, Errno>
1662+
fn read_internal<R>(&self, current_task: &CurrentTask, read: R) -> Result<usize, Errno>
16611663
where
16621664
R: FnOnce() -> Result<usize, Errno>,
16631665
{
1666+
security::file_permission(current_task, self, security::PermissionFlags::READ)?;
1667+
16641668
if !self.can_read() {
16651669
return error!(EBADF);
16661670
}
@@ -1685,7 +1689,7 @@ impl FileObject {
16851689
where
16861690
L: LockEqualOrBefore<FileOpsCore>,
16871691
{
1688-
self.read_internal(|| {
1692+
self.read_internal(current_task, || {
16891693
let mut locked = locked.cast_locked::<FileOpsCore>();
16901694
if !self.ops().has_persistent_offsets() {
16911695
if data.available() > MAX_LFS_FILESIZE {
@@ -1718,7 +1722,9 @@ impl FileObject {
17181722
}
17191723
checked_add_offset_and_length(offset, data.available())?;
17201724
let mut locked = locked.cast_locked::<FileOpsCore>();
1721-
self.read_internal(|| self.ops.read(&mut locked, self, current_task, offset, data))
1725+
self.read_internal(current_task, || {
1726+
self.ops.read(&mut locked, self, current_task, offset, data)
1727+
})
17221728
}
17231729

17241730
/// Common checks before calling ops().write.
@@ -1732,6 +1738,8 @@ impl FileObject {
17321738
where
17331739
L: LockEqualOrBefore<FileOpsCore>,
17341740
{
1741+
security::file_permission(current_task, self, security::PermissionFlags::WRITE)?;
1742+
17351743
// We need to cap the size of `data` to prevent us from growing the file too large,
17361744
// according to <https://man7.org/linux/man-pages/man2/write.2.html>:
17371745
//

src/starnix/tests/selinux/expectations/selinux.json5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"*entrypoint*",
1717
"*execshare*",
1818

19-
// TODO(http://b/364568874): `inherit` tests will pass once the `file_security_ops`
19+
// TODO(http://b/385121365): `inherit` tests will pass once the `file_permission`
2020
// hook correctly enforces the `write` permission.
2121
"*inherit*",
2222

0 commit comments

Comments
 (0)