blob: 2485ab5723233cae16c0f5113b687d492539da75 [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::{
32 close, fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write,
Janis Danisevskis04945eb2021-12-06 17:20:09 -080033 ForkResult, Gid, 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;
Janis Danisevskisa578d392021-09-20 15:44:06 -070038use std::os::unix::io::RawFd;
39
40fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) {
41 setgid(gid).expect("Failed to set GID. This test might need more privileges.");
42 setuid(uid).expect("Failed to set UID. This test might need more privileges.");
43
44 selinux::setcon(&se_context)
45 .expect("Failed to set SELinux context. This test might need more privileges.");
46}
47
48/// PipeReader is a simple wrapper around raw pipe file descriptors.
49/// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which
50/// reads from the pipe into an expending vector, until no more data can be read.
51struct PipeReader(RawFd);
52
Janis Danisevskis04945eb2021-12-06 17:20:09 -080053impl Read for PipeReader {
54 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
55 let bytes = nix_read(self.0, buf)?;
56 Ok(bytes)
Janis Danisevskisa578d392021-09-20 15:44:06 -070057 }
58}
59
60impl Drop for PipeReader {
61 fn drop(&mut self) {
62 close(self.0).expect("Failed to close reader pipe fd.");
63 }
64}
65
66/// PipeWriter is a simple wrapper around raw pipe file descriptors.
67/// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which
68/// writes the given buffer into the pipe, returning the number of bytes written.
69struct PipeWriter(RawFd);
70
Janis Danisevskisa578d392021-09-20 15:44:06 -070071impl Drop for PipeWriter {
72 fn drop(&mut self) {
73 close(self.0).expect("Failed to close writer pipe fd.");
74 }
75}
76
Janis Danisevskis04945eb2021-12-06 17:20:09 -080077impl Write for PipeWriter {
78 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
79 let written = nix_write(self.0, buf)?;
80 Ok(written)
81 }
82
83 fn flush(&mut self) -> std::io::Result<()> {
84 // Flush is a NO-OP.
85 Ok(())
86 }
87}
88
89/// Denotes the sender side of a serializing channel.
90pub struct ChannelWriter<T: Serialize + DeserializeOwned>(PipeWriter, PhantomData<T>);
91
92impl<T: Serialize + DeserializeOwned> ChannelWriter<T> {
93 /// Sends a serializable object to a the corresponding ChannelReader.
94 /// Sending is always non blocking. Panics if any error occurs during io or serialization.
95 pub fn send(&mut self, value: &T) {
96 let serialized = serde_cbor::to_vec(value)
97 .expect("In ChannelWriter::send: Failed to serialize to vector.");
98 let size = serialized.len().to_be_bytes();
99 match self.0.write(&size).expect("In ChannelWriter::send: Failed to write serialized size.")
100 {
101 w if w != std::mem::size_of::<usize>() => {
102 panic!(
103 "In ChannelWriter::send: Failed to write serialized size. (written: {}).",
104 w
105 );
106 }
107 _ => {}
108 };
109 match self
110 .0
111 .write(&serialized)
112 .expect("In ChannelWriter::send: Failed to write serialized data.")
113 {
114 w if w != serialized.len() => {
115 panic!(
116 "In ChannelWriter::send: Failed to write serialized data. (written: {}).",
117 w
118 );
119 }
120 _ => {}
121 };
122 }
123}
124
125/// Represents the receiving and of a serializing channel.
126pub struct ChannelReader<T>(PipeReader, PhantomData<T>);
127
128impl<T: Serialize + DeserializeOwned> ChannelReader<T> {
129 /// Receives a serializable object from the corresponding ChannelWriter.
130 /// Receiving blocks until an object of type T has been read from the channel.
131 /// Panics if an error occurs during io or deserialization.
132 pub fn recv(&mut self) -> T {
133 let mut size_buffer = [0u8; std::mem::size_of::<usize>()];
134 match self.0.read(&mut size_buffer).expect("In ChannelReader::recv: Failed to read size.") {
135 r if r != size_buffer.len() => {
136 panic!("In ChannelReader::recv: Failed to read size. Insufficient data: {}", r);
137 }
138 _ => {}
139 };
140 let size = usize::from_be_bytes(size_buffer);
141 let mut data_buffer = vec![0u8; size];
142 match self
143 .0
144 .read(&mut data_buffer)
145 .expect("In ChannelReader::recv: Failed to read serialized data.")
146 {
147 r if r != data_buffer.len() => {
148 panic!(
149 "In ChannelReader::recv: Failed to read serialized data. Insufficient data: {}",
150 r
151 );
152 }
153 _ => {}
154 };
155
156 serde_cbor::from_slice(&data_buffer)
157 .expect("In ChannelReader::recv: Failed to deserialize data.")
158 }
159}
160
Janis Danisevskisa578d392021-09-20 15:44:06 -0700161fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> {
162 let (read_fd, write_fd) = nix_pipe()?;
163 Ok((PipeReader(read_fd), PipeWriter(write_fd)))
164}
165
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800166fn pipe_channel<T>() -> Result<(ChannelReader<T>, ChannelWriter<T>), nix::Error>
167where
168 T: Serialize + DeserializeOwned,
169{
170 let (reader, writer) = pipe()?;
171 Ok((
172 ChannelReader::<T>(reader, Default::default()),
173 ChannelWriter::<T>(writer, Default::default()),
174 ))
175}
176
177/// Handle for handling child processes.
178pub struct ChildHandle<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> {
179 pid: Pid,
180 result_reader: ChannelReader<R>,
181 cmd_writer: ChannelWriter<M>,
182 response_reader: ChannelReader<M>,
183 exit_status: Option<WaitStatus>,
184}
185
186impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> ChildHandle<R, M> {
187 /// Send a command message to the child.
188 pub fn send(&mut self, data: &M) {
189 self.cmd_writer.send(data)
190 }
191
192 /// Receive a response from the child.
193 pub fn recv(&mut self) -> M {
194 self.response_reader.recv()
195 }
196
197 /// Get child result. Panics if the child did not exit with status 0 or if a serialization
198 /// error occurred.
199 pub fn get_result(mut self) -> R {
200 let status =
201 waitpid(self.pid, None).expect("ChildHandle::wait: Failed while waiting for child.");
202 match status {
203 WaitStatus::Exited(pid, 0) => {
204 // Child exited successfully.
205 // Read the result from the pipe.
206 self.exit_status = Some(WaitStatus::Exited(pid, 0));
207 self.result_reader.recv()
208 }
209 WaitStatus::Exited(pid, c) => {
210 panic!("Child did not exit as expected: {:?}", WaitStatus::Exited(pid, c));
211 }
212 status => {
213 panic!("Child did not exit at all: {:?}", status);
214 }
215 }
216 }
217}
218
219impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> {
220 fn drop(&mut self) {
221 if self.exit_status.is_none() {
222 panic!("Child result not checked.")
223 }
224 }
225}
226
227/// Run the given closure in a new process running with the new identity given as
228/// `uid`, `gid`, and `se_context`. Parent process will run without waiting for child status.
229///
230/// # Safety
231/// run_as_child runs the given closure in the client branch of fork. And it uses non
232/// async signal safe API. This means that calling this function in a multi threaded program
233/// yields undefined behavior in the child. As of this writing, it is safe to call this function
234/// from a Rust device test, because every test itself is spawned as a separate process.
235///
236/// # Safety Binder
237/// It is okay for the closure to use binder services, however, this does not work
238/// if the parent initialized libbinder already. So do not use binder outside of the closure
239/// in your test.
240pub unsafe fn run_as_child<F, R, M>(
241 se_context: &str,
242 uid: Uid,
243 gid: Gid,
244 f: F,
245) -> Result<ChildHandle<R, M>, nix::Error>
246where
247 R: Serialize + DeserializeOwned,
248 M: Serialize + DeserializeOwned,
249 F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R,
250{
251 let se_context =
252 selinux::Context::new(se_context).expect("Unable to construct selinux::Context.");
253 let (result_reader, mut result_writer) = pipe_channel().expect("Failed to create pipe.");
254 let (mut cmd_reader, cmd_writer) = pipe_channel().expect("Failed to create cmd pipe.");
255 let (response_reader, mut response_writer) =
256 pipe_channel().expect("Failed to create cmd pipe.");
257
258 match fork() {
259 Ok(ForkResult::Parent { child, .. }) => {
260 drop(response_writer);
261 drop(cmd_reader);
262 drop(result_writer);
263
264 Ok(ChildHandle::<R, M> {
265 pid: child,
266 result_reader,
267 response_reader,
268 cmd_writer,
269 exit_status: None,
270 })
271 }
272 Ok(ForkResult::Child) => {
273 drop(cmd_writer);
274 drop(response_reader);
275 drop(result_reader);
276
277 // This will panic on error or insufficient privileges.
278 transition(se_context, uid, gid);
279
280 // Run the closure.
281 let result = f(&mut cmd_reader, &mut response_writer);
282
283 // Serialize the result of the closure.
284 result_writer.send(&result);
285
286 // Set exit status to `0`.
287 std::process::exit(0);
288 }
289 Err(errno) => {
290 panic!("Failed to fork: {:?}", errno);
291 }
292 }
293}
294
Janis Danisevskisa578d392021-09-20 15:44:06 -0700295/// Run the given closure in a new process running with the new identity given as
296/// `uid`, `gid`, and `se_context`.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800297///
298/// # Safety
299/// run_as runs the given closure in the client branch of fork. And it uses non
300/// async signal safe API. This means that calling this function in a multi threaded program
301/// yields undefined behavior in the child. As of this writing, it is safe to call this function
302/// from a Rust device test, because every test itself is spawned as a separate process.
303///
304/// # Safety Binder
305/// It is okay for the closure to use binder services, however, this does not work
306/// if the parent initialized libbinder already. So do not use binder outside of the closure
307/// in your test.
308pub unsafe fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R
Janis Danisevskisa578d392021-09-20 15:44:06 -0700309where
310 R: Serialize + DeserializeOwned,
311 F: 'static + Send + FnOnce() -> R,
312{
313 let se_context =
314 selinux::Context::new(se_context).expect("Unable to construct selinux::Context.");
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800315 let (mut reader, mut writer) = pipe_channel::<R>().expect("Failed to create pipe.");
Janis Danisevskisa578d392021-09-20 15:44:06 -0700316
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800317 match fork() {
Janis Danisevskisa578d392021-09-20 15:44:06 -0700318 Ok(ForkResult::Parent { child, .. }) => {
319 drop(writer);
320 let status = waitpid(child, None).expect("Failed while waiting for child.");
321 if let WaitStatus::Exited(_, 0) = status {
322 // Child exited successfully.
323 // Read the result from the pipe.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800324 // let serialized_result =
325 // reader.read_all().expect("Failed to read result from child.");
Janis Danisevskisa578d392021-09-20 15:44:06 -0700326
327 // Deserialize the result and return it.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800328 reader.recv()
Janis Danisevskisa578d392021-09-20 15:44:06 -0700329 } else {
330 panic!("Child did not exit as expected {:?}", status);
331 }
332 }
333 Ok(ForkResult::Child) => {
334 // This will panic on error or insufficient privileges.
335 transition(se_context, uid, gid);
336
337 // Run the closure.
338 let result = f();
339
340 // Serialize the result of the closure.
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800341 writer.send(&result);
Janis Danisevskisa578d392021-09-20 15:44:06 -0700342
343 // Set exit status to `0`.
344 std::process::exit(0);
345 }
346 Err(errno) => {
347 panic!("Failed to fork: {:?}", errno);
348 }
349 }
350}
351
352#[cfg(test)]
353mod test {
354 use super::*;
355 use keystore2_selinux as selinux;
356 use nix::unistd::{getgid, getuid};
357 use serde::{Deserialize, Serialize};
358
359 /// This test checks that the closure does not produce an exit status of `0` when run inside a
360 /// test and the closure panics. This would mask test failures as success.
361 #[test]
362 #[should_panic]
363 fn test_run_as_panics_on_closure_panic() {
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800364 // Safety: run_as must be called from a single threaded process.
365 // This device test is run as a separate single threaded process.
366 unsafe {
367 run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || {
368 panic!("Closure must panic.")
369 })
370 };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700371 }
372
373 static TARGET_UID: Uid = Uid::from_raw(10020);
374 static TARGET_GID: Gid = Gid::from_raw(10020);
375 static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
376
377 /// Tests that the closure is running as the target identity.
378 #[test]
379 fn test_transition_to_untrusted_app() {
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800380 // Safety: run_as must be called from a single threaded process.
381 // This device test is run as a separate single threaded process.
382 unsafe {
383 run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || {
384 assert_eq!(TARGET_UID, getuid());
385 assert_eq!(TARGET_GID, getgid());
386 assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap());
387 })
388 };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700389 }
390
391 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
392 struct SomeResult {
393 a: u32,
394 b: u64,
395 c: String,
396 }
397
398 #[test]
399 fn test_serialized_result() {
400 let test_result = SomeResult {
401 a: 5,
402 b: 0xffffffffffffffff,
403 c: "supercalifragilisticexpialidocious".to_owned(),
404 };
405 let test_result_clone = test_result.clone();
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800406 // Safety: run_as must be called from a single threaded process.
407 // This device test is run as a separate single threaded process.
408 let result = unsafe { run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || test_result_clone) };
Janis Danisevskisa578d392021-09-20 15:44:06 -0700409 assert_eq!(test_result, result);
410 }
Janis Danisevskis04945eb2021-12-06 17:20:09 -0800411
412 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
413 enum PingPong {
414 Ping,
415 Pong,
416 }
417
418 /// Tests that closure is running under given user identity and communicates with calling
419 /// process using pipe.
420 #[test]
421 fn test_run_as_child() {
422 let test_result = SomeResult {
423 a: 5,
424 b: 0xffffffffffffffff,
425 c: "supercalifragilisticexpialidocious".to_owned(),
426 };
427 let test_result_clone = test_result.clone();
428
429 // Safety: run_as_child must be called from a single threaded process.
430 // This device test is run as a separate single threaded process.
431 let mut child_handle: ChildHandle<SomeResult, PingPong> = unsafe {
432 run_as_child(TARGET_CTX, TARGET_UID, TARGET_GID, |cmd_reader, response_writer| {
433 assert_eq!(TARGET_UID, getuid());
434 assert_eq!(TARGET_GID, getgid());
435 assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap());
436
437 let ping: PingPong = cmd_reader.recv();
438 assert_eq!(ping, PingPong::Ping);
439
440 response_writer.send(&PingPong::Pong);
441
442 let ping: PingPong = cmd_reader.recv();
443 assert_eq!(ping, PingPong::Ping);
444 let pong: PingPong = cmd_reader.recv();
445 assert_eq!(pong, PingPong::Pong);
446
447 response_writer.send(&PingPong::Pong);
448 response_writer.send(&PingPong::Ping);
449
450 test_result_clone
451 })
452 .unwrap()
453 };
454
455 // Send one ping.
456 child_handle.send(&PingPong::Ping);
457
458 // Expect one pong.
459 let pong = child_handle.recv();
460 assert_eq!(pong, PingPong::Pong);
461
462 // Send ping and pong.
463 child_handle.send(&PingPong::Ping);
464 child_handle.send(&PingPong::Pong);
465
466 // Expect pong and ping.
467 let pong = child_handle.recv();
468 assert_eq!(pong, PingPong::Pong);
469 let ping = child_handle.recv();
470 assert_eq!(ping, PingPong::Ping);
471
472 assert_eq!(child_handle.get_result(), test_result);
473 }
Janis Danisevskisa578d392021-09-20 15:44:06 -0700474}