blob: 2cd9fec351f4cfc1f32c0e6e57d9b5b201fb9263 [file] [log] [blame]
Janis Danisevskisa578d392021-09-20 15:44:06 -07001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module is intended for testing access control enforcement of services such as keystore2,
16//! by assuming various identities with varying levels of privilege. Consequently, appropriate
17//! privileges are required, or the attempt will fail causing a panic.
18//! The `run_as` module provides the function `run_as`, which takes a UID, GID, an SELinux
19//! context, and a closure. The return type of the closure, which is also the return type of
20//! `run_as`, must implement `serde::Serialize` and `serde::Deserialize`.
21//! `run_as` forks, transitions to the given identity, and executes the closure in the newly
22//! forked process. If the closure returns, i.e., does not panic, the forked process exits with
23//! a status of `0`, and the return value is serialized and sent through a pipe to the parent where
24//! it gets deserialized and returned. The STDIO is not changed and the parent's panic handler
25//! remains unchanged. So if the closure panics, the panic message is printed on the parent's STDERR
26//! and the exit status is set to a non `0` value. The latter causes the parent to panic as well,
27//! and if run in a test context, the test to fail.
28
29use keystore2_selinux as selinux;
30use nix::sys::wait::{waitpid, WaitStatus};
31use nix::unistd::{
Frederick Mayle42632072024-04-08 16:51:40 -070032 fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, ForkResult, Gid,
33 Pid, Uid,
Janis Danisevskisa578d392021-09-20 15:44:06 -070034};
35use serde::{de::DeserializeOwned, Serialize};
Janis Danisevskis04945eb2021-12-06 17:20:09 -080036use std::io::{Read, Write};
37use std::marker::PhantomData;
Frederick Mayle42632072024-04-08 16:51:40 -070038use std::os::fd::AsRawFd;
39use std::os::fd::OwnedFd;
Janis Danisevskisa578d392021-09-20 15:44:06 -070040
41fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) {
42 setgid(gid).expect("Failed to set GID. This test might need more privileges.");
43 setuid(uid).expect("Failed to set UID. This test might need more privileges.");
44
45 selinux::setcon(&se_context)
46 .expect("Failed to set SELinux context. This test might need more privileges.");
47}
48
49/// PipeReader is a simple wrapper around raw pipe file descriptors.
50/// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which
51/// reads from the pipe into an expending vector, until no more data can be read.
Frederick Mayle42632072024-04-08 16:51:40 -070052struct PipeReader(OwnedFd);
Janis Danisevskisa578d392021-09-20 15:44:06 -070053
Janis Danisevskis04945eb2021-12-06 17:20:09 -080054impl Read for PipeReader {
55 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
Frederick Mayle42632072024-04-08 16:51:40 -070056 let bytes = nix_read(self.0.as_raw_fd(), buf)?;
Janis Danisevskis04945eb2021-12-06 17:20:09 -080057 Ok(bytes)
Janis Danisevskisa578d392021-09-20 15:44:06 -070058 }
59}
60
Janis Danisevskisa578d392021-09-20 15:44:06 -070061/// PipeWriter is a simple wrapper around raw pipe file descriptors.
62/// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which
63/// writes the given buffer into the pipe, returning the number of bytes written.
Frederick Mayle42632072024-04-08 16:51:40 -070064struct PipeWriter(OwnedFd);
Janis Danisevskisa578d392021-09-20 15:44:06 -070065
Janis Danisevskis04945eb2021-12-06 17:20:09 -080066impl Write for PipeWriter {
67 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Frederick Mayle42632072024-04-08 16:51:40 -070068 let written = nix_write(&self.0, buf)?;
Janis Danisevskis04945eb2021-12-06 17:20:09 -080069 Ok(written)
70 }
71
72 fn flush(&mut self) -> std::io::Result<()> {
73 // Flush is a NO-OP.
74 Ok(())
75 }
76}
77
78/// Denotes the sender side of a serializing channel.
79pub struct ChannelWriter<T: Serialize + DeserializeOwned>(PipeWriter, PhantomData<T>);
80
81impl<T: Serialize + DeserializeOwned> ChannelWriter<T> {
82 /// Sends a serializable object to a the corresponding ChannelReader.
83 /// Sending is always non blocking. Panics if any error occurs during io or serialization.
84 pub fn send(&mut self, value: &T) {
85 let serialized = serde_cbor::to_vec(value)
86 .expect("In ChannelWriter::send: Failed to serialize to vector.");
87 let size = serialized.len().to_be_bytes();
88 match self.0.write(&size).expect("In ChannelWriter::send: Failed to write serialized size.")
89 {
90 w if w != std::mem::size_of::<usize>() => {
91 panic!(
92 "In ChannelWriter::send: Failed to write serialized size. (written: {}).",
93 w
94 );
95 }
96 _ => {}
97 };
98 match self
99 .0
100 .write(&serialized)
101 .expect("In ChannelWriter::send: Failed to write serialized data.")
102 {
103 w if w != serialized.len() => {
104 panic!(
105 "In ChannelWriter::send: Failed to write serialized data. (written: {}).",
106 w
107 );
108 }
109 _ => {}
110 };
111 }
112}
113
114/// Represents the receiving and of a serializing channel.
115pub struct ChannelReader<T>(PipeReader, PhantomData<T>);
116
117impl<T: Serialize + DeserializeOwned> ChannelReader<T> {
118 /// Receives a serializable object from the corresponding ChannelWriter.
119 /// Receiving blocks until an object of type T has been read from the channel.
120 /// Panics if an error occurs during io or deserialization.
121 pub fn recv(&mut self) -> T {
122 let mut size_buffer = [0u8; std::mem::size_of::<usize>()];
123 match self.0.read(&mut size_buffer).expect("In ChannelReader::recv: Failed to read size.") {
124 r if r != size_buffer.len() => {
125 panic!("In ChannelReader::recv: Failed to read size. Insufficient data: {}", r);
126 }
127 _ => {}
128 };
129 let size = usize::from_be_bytes(size_buffer);
130 let mut data_buffer = vec![0u8; size];
131 match self
132 .0
133 .read(&mut data_buffer)
134 .expect("In ChannelReader::recv: Failed to read serialized data.")
135 {
136 r if r != data_buffer.len() => {
137 panic!(
138 "In ChannelReader::recv: Failed to read serialized data. Insufficient data: {}",
139 r
140 );
141 }
142 _ => {}
143 };
144
145 serde_cbor::from_slice(&data_buffer)
146 .expect("In ChannelReader::recv: Failed to deserialize data.")
147 }
148}
149
Janis Danisevskisa578d392021-09-20 15:44:06 -0700150fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> {
151 let (read_fd, write_fd) = nix_pipe()?;
152 Ok((PipeReader(read_fd), PipeWriter(write_fd)))
153}
154
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800155fn pipe_channel<T>() -> Result<(ChannelReader<T>, ChannelWriter<T>), nix::Error>
156where
157 T: Serialize + DeserializeOwned,
158{
159 let (reader, writer) = pipe()?;
160 Ok((
161 ChannelReader::<T>(reader, Default::default()),
162 ChannelWriter::<T>(writer, Default::default()),
163 ))
164}
165
166/// Handle for handling child processes.
167pub struct ChildHandle<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> {
168 pid: Pid,
169 result_reader: ChannelReader<R>,
170 cmd_writer: ChannelWriter<M>,
171 response_reader: ChannelReader<M>,
172 exit_status: Option<WaitStatus>,
173}
174
175impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> ChildHandle<R, M> {
176 /// Send a command message to the child.
177 pub fn send(&mut self, data: &M) {
178 self.cmd_writer.send(data)
179 }
180
181 /// Receive a response from the child.
182 pub fn recv(&mut self) -> M {
183 self.response_reader.recv()
184 }
185
186 /// Get child result. Panics if the child did not exit with status 0 or if a serialization
187 /// error occurred.
188 pub fn get_result(mut self) -> R {
189 let status =
190 waitpid(self.pid, None).expect("ChildHandle::wait: Failed while waiting for child.");
191 match status {
192 WaitStatus::Exited(pid, 0) => {
193 // Child exited successfully.
194 // Read the result from the pipe.
195 self.exit_status = Some(WaitStatus::Exited(pid, 0));
196 self.result_reader.recv()
197 }
198 WaitStatus::Exited(pid, c) => {
199 panic!("Child did not exit as expected: {:?}", WaitStatus::Exited(pid, c));
200 }
201 status => {
202 panic!("Child did not exit at all: {:?}", status);
203 }
204 }
205 }
206}
207
208impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> {
209 fn drop(&mut self) {
210 if self.exit_status.is_none() {
211 panic!("Child result not checked.")
212 }
213 }
214}
215
216/// Run the given closure in a new process running with the new identity given as
217/// `uid`, `gid`, and `se_context`. Parent process will run without waiting for child status.
218///
219/// # Safety
220/// run_as_child runs the given closure in the client branch of fork. And it uses non
221/// async signal safe API. This means that calling this function in a multi threaded program
222/// yields undefined behavior in the child. As of this writing, it is safe to call this function
223/// from a Rust device test, because every test itself is spawned as a separate process.
224///
225/// # Safety Binder
226/// It is okay for the closure to use binder services, however, this does not work
227/// if the parent initialized libbinder already. So do not use binder outside of the closure
228/// in your test.
229pub unsafe fn run_as_child<F, R, M>(
230 se_context: &str,
231 uid: Uid,
232 gid: Gid,
233 f: F,
234) -> Result<ChildHandle<R, M>, nix::Error>
235where
236 R: Serialize + DeserializeOwned,
237 M: Serialize + DeserializeOwned,
238 F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R,
239{
240 let se_context =
241 selinux::Context::new(se_context).expect("Unable to construct selinux::Context.");
242 let (result_reader, mut result_writer) = pipe_channel().expect("Failed to create pipe.");
243 let (mut cmd_reader, cmd_writer) = pipe_channel().expect("Failed to create cmd pipe.");
244 let (response_reader, mut response_writer) =
245 pipe_channel().expect("Failed to create cmd pipe.");
246
Andrew Walbrana47698a2023-07-21 17:23:56 +0100247 // SAFETY: Our caller guarantees that the process only has a single thread, so calling
248 // non-async-signal-safe functions in the child is in fact safe.
249 match unsafe { fork() } {
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800250 Ok(ForkResult::Parent { child, .. }) => {
251 drop(response_writer);
252 drop(cmd_reader);
253 drop(result_writer);
254
255 Ok(ChildHandle::<R, M> {
256 pid: child,
257 result_reader,
258 response_reader,
259 cmd_writer,
260 exit_status: None,
261 })
262 }
263 Ok(ForkResult::Child) => {
264 drop(cmd_writer);
265 drop(response_reader);
266 drop(result_reader);
267
268 // This will panic on error or insufficient privileges.
269 transition(se_context, uid, gid);
270
271 // Run the closure.
272 let result = f(&mut cmd_reader, &mut response_writer);
273
274 // Serialize the result of the closure.
275 result_writer.send(&result);
276
277 // Set exit status to `0`.
278 std::process::exit(0);
279 }
280 Err(errno) => {
281 panic!("Failed to fork: {:?}", errno);
282 }
283 }
284}
285
Janis Danisevskisa578d392021-09-20 15:44:06 -0700286/// Run the given closure in a new process running with the new identity given as
287/// `uid`, `gid`, and `se_context`.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800288///
289/// # Safety
290/// run_as runs the given closure in the client branch of fork. And it uses non
291/// async signal safe API. This means that calling this function in a multi threaded program
292/// yields undefined behavior in the child. As of this writing, it is safe to call this function
293/// from a Rust device test, because every test itself is spawned as a separate process.
294///
295/// # Safety Binder
296/// It is okay for the closure to use binder services, however, this does not work
297/// if the parent initialized libbinder already. So do not use binder outside of the closure
298/// in your test.
299pub unsafe fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R
Janis Danisevskisa578d392021-09-20 15:44:06 -0700300where
301 R: Serialize + DeserializeOwned,
302 F: 'static + Send + FnOnce() -> R,
303{
304 let se_context =
305 selinux::Context::new(se_context).expect("Unable to construct selinux::Context.");
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800306 let (mut reader, mut writer) = pipe_channel::<R>().expect("Failed to create pipe.");
Janis Danisevskisa578d392021-09-20 15:44:06 -0700307
Andrew Walbrana47698a2023-07-21 17:23:56 +0100308 // SAFETY: Our caller guarantees that the process only has a single thread, so calling
309 // non-async-signal-safe functions in the child is in fact safe.
310 match unsafe { fork() } {
Janis Danisevskisa578d392021-09-20 15:44:06 -0700311 Ok(ForkResult::Parent { child, .. }) => {
312 drop(writer);
313 let status = waitpid(child, None).expect("Failed while waiting for child.");
314 if let WaitStatus::Exited(_, 0) = status {
315 // Child exited successfully.
316 // Read the result from the pipe.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800317 // let serialized_result =
318 // reader.read_all().expect("Failed to read result from child.");
Janis Danisevskisa578d392021-09-20 15:44:06 -0700319
320 // Deserialize the result and return it.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800321 reader.recv()
Janis Danisevskisa578d392021-09-20 15:44:06 -0700322 } else {
323 panic!("Child did not exit as expected {:?}", status);
324 }
325 }
326 Ok(ForkResult::Child) => {
327 // This will panic on error or insufficient privileges.
328 transition(se_context, uid, gid);
329
330 // Run the closure.
331 let result = f();
332
333 // Serialize the result of the closure.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800334 writer.send(&result);
Janis Danisevskisa578d392021-09-20 15:44:06 -0700335
336 // Set exit status to `0`.
337 std::process::exit(0);
338 }
339 Err(errno) => {
340 panic!("Failed to fork: {:?}", errno);
341 }
342 }
343}
344
345#[cfg(test)]
346mod test {
347 use super::*;
348 use keystore2_selinux as selinux;
349 use nix::unistd::{getgid, getuid};
350 use serde::{Deserialize, Serialize};
351
352 /// This test checks that the closure does not produce an exit status of `0` when run inside a
353 /// test and the closure panics. This would mask test failures as success.
354 #[test]
355 #[should_panic]
356 fn test_run_as_panics_on_closure_panic() {
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800357 // Safety: run_as must be called from a single threaded process.
358 // This device test is run as a separate single threaded process.
359 unsafe {
Chris Wailes00fa9bd2024-09-05 13:33:08 -0700360 run_as::<_, ()>(
361 selinux::getcon().unwrap().to_str().unwrap(),
362 getuid(),
363 getgid(),
364 || panic!("Closure must panic."),
365 )
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800366 };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700367 }
368
369 static TARGET_UID: Uid = Uid::from_raw(10020);
370 static TARGET_GID: Gid = Gid::from_raw(10020);
371 static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
372
373 /// Tests that the closure is running as the target identity.
374 #[test]
375 fn test_transition_to_untrusted_app() {
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800376 // Safety: run_as must be called from a single threaded process.
377 // This device test is run as a separate single threaded process.
378 unsafe {
379 run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || {
380 assert_eq!(TARGET_UID, getuid());
381 assert_eq!(TARGET_GID, getgid());
382 assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap());
383 })
384 };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700385 }
386
387 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
388 struct SomeResult {
389 a: u32,
390 b: u64,
391 c: String,
392 }
393
394 #[test]
395 fn test_serialized_result() {
396 let test_result = SomeResult {
397 a: 5,
398 b: 0xffffffffffffffff,
399 c: "supercalifragilisticexpialidocious".to_owned(),
400 };
401 let test_result_clone = test_result.clone();
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800402 // Safety: run_as must be called from a single threaded process.
403 // This device test is run as a separate single threaded process.
404 let result = unsafe { run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || test_result_clone) };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700405 assert_eq!(test_result, result);
406 }
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800407
408 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409 enum PingPong {
410 Ping,
411 Pong,
412 }
413
414 /// Tests that closure is running under given user identity and communicates with calling
415 /// process using pipe.
416 #[test]
417 fn test_run_as_child() {
418 let test_result = SomeResult {
419 a: 5,
420 b: 0xffffffffffffffff,
421 c: "supercalifragilisticexpialidocious".to_owned(),
422 };
423 let test_result_clone = test_result.clone();
424
425 // Safety: run_as_child must be called from a single threaded process.
426 // This device test is run as a separate single threaded process.
427 let mut child_handle: ChildHandle<SomeResult, PingPong> = unsafe {
428 run_as_child(TARGET_CTX, TARGET_UID, TARGET_GID, |cmd_reader, response_writer| {
429 assert_eq!(TARGET_UID, getuid());
430 assert_eq!(TARGET_GID, getgid());
431 assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap());
432
433 let ping: PingPong = cmd_reader.recv();
434 assert_eq!(ping, PingPong::Ping);
435
436 response_writer.send(&PingPong::Pong);
437
438 let ping: PingPong = cmd_reader.recv();
439 assert_eq!(ping, PingPong::Ping);
440 let pong: PingPong = cmd_reader.recv();
441 assert_eq!(pong, PingPong::Pong);
442
443 response_writer.send(&PingPong::Pong);
444 response_writer.send(&PingPong::Ping);
445
446 test_result_clone
447 })
448 .unwrap()
449 };
450
451 // Send one ping.
452 child_handle.send(&PingPong::Ping);
453
454 // Expect one pong.
455 let pong = child_handle.recv();
456 assert_eq!(pong, PingPong::Pong);
457
458 // Send ping and pong.
459 child_handle.send(&PingPong::Ping);
460 child_handle.send(&PingPong::Pong);
461
462 // Expect pong and ping.
463 let pong = child_handle.recv();
464 assert_eq!(pong, PingPong::Pong);
465 let ping = child_handle.recv();
466 assert_eq!(ping, PingPong::Ping);
467
468 assert_eq!(child_handle.get_result(), test_result);
469 }
Janis Danisevskisa578d392021-09-20 15:44:06 -0700470}