• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // 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 
29 use keystore2_selinux as selinux;
30 use nix::sys::wait::{waitpid, WaitStatus};
31 use nix::unistd::{
32     close, fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write,
33     ForkResult, Gid, Pid, Uid,
34 };
35 use serde::{de::DeserializeOwned, Serialize};
36 use std::io::{Read, Write};
37 use std::marker::PhantomData;
38 use std::os::unix::io::RawFd;
39 
transition(se_context: selinux::Context, uid: Uid, gid: Gid)40 fn 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.
51 struct PipeReader(RawFd);
52 
53 impl Read for PipeReader {
read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>54     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
55         let bytes = nix_read(self.0, buf)?;
56         Ok(bytes)
57     }
58 }
59 
60 impl Drop for PipeReader {
drop(&mut self)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.
69 struct PipeWriter(RawFd);
70 
71 impl Drop for PipeWriter {
drop(&mut self)72     fn drop(&mut self) {
73         close(self.0).expect("Failed to close writer pipe fd.");
74     }
75 }
76 
77 impl Write for PipeWriter {
write(&mut self, buf: &[u8]) -> std::io::Result<usize>78     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
79         let written = nix_write(self.0, buf)?;
80         Ok(written)
81     }
82 
flush(&mut self) -> std::io::Result<()>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.
90 pub struct ChannelWriter<T: Serialize + DeserializeOwned>(PipeWriter, PhantomData<T>);
91 
92 impl<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.
send(&mut self, value: &T)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.
126 pub struct ChannelReader<T>(PipeReader, PhantomData<T>);
127 
128 impl<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.
recv(&mut self) -> T132     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 
pipe() -> Result<(PipeReader, PipeWriter), nix::Error>161 fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> {
162     let (read_fd, write_fd) = nix_pipe()?;
163     Ok((PipeReader(read_fd), PipeWriter(write_fd)))
164 }
165 
pipe_channel<T>() -> Result<(ChannelReader<T>, ChannelWriter<T>), nix::Error> where T: Serialize + DeserializeOwned,166 fn pipe_channel<T>() -> Result<(ChannelReader<T>, ChannelWriter<T>), nix::Error>
167 where
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.
178 pub 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 
186 impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> ChildHandle<R, M> {
187     /// Send a command message to the child.
send(&mut self, data: &M)188     pub fn send(&mut self, data: &M) {
189         self.cmd_writer.send(data)
190     }
191 
192     /// Receive a response from the child.
recv(&mut self) -> M193     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.
get_result(mut self) -> R199     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 
219 impl<R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned> Drop for ChildHandle<R, M> {
drop(&mut self)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.
run_as_child<F, R, M>( se_context: &str, uid: Uid, gid: Gid, f: F, ) -> Result<ChildHandle<R, M>, nix::Error> where R: Serialize + DeserializeOwned, M: Serialize + DeserializeOwned, F: 'static + Send + FnOnce(&mut ChannelReader<M>, &mut ChannelWriter<M>) -> R,240 pub 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>
246 where
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 
295 /// Run the given closure in a new process running with the new identity given as
296 /// `uid`, `gid`, and `se_context`.
297 ///
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.
run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R where R: Serialize + DeserializeOwned, F: 'static + Send + FnOnce() -> R,308 pub unsafe fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R
309 where
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.");
315     let (mut reader, mut writer) = pipe_channel::<R>().expect("Failed to create pipe.");
316 
317     match fork() {
318         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.
324                 // let serialized_result =
325                 //     reader.read_all().expect("Failed to read result from child.");
326 
327                 // Deserialize the result and return it.
328                 reader.recv()
329             } 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.
341             writer.send(&result);
342 
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)]
353 mod 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]
test_run_as_panics_on_closure_panic()363     fn test_run_as_panics_on_closure_panic() {
364         // 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         };
371     }
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]
test_transition_to_untrusted_app()379     fn test_transition_to_untrusted_app() {
380         // 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         };
389     }
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]
test_serialized_result()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();
406         // 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) };
409         assert_eq!(test_result, result);
410     }
411 
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]
test_run_as_child()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     }
474 }
475