test_vmo_backed_block_server/
lib.rs1use anyhow::{Error, anyhow};
6use block_server::async_interface::SessionManager;
7use block_server::{BlockInfo, BlockServer, DeviceInfo};
8#[cfg(feature = "for-testing")]
9use fidl::endpoints::RequestStream;
10use fidl::endpoints::{ClientEnd, FromClient, ServerEnd};
11#[cfg(feature = "for-testing")]
12use fidl_fuchsia_hardware_inlineencryption::{DeviceMarker, DeviceRequest, DeviceRequestStream};
13use fidl_fuchsia_storage_block as fblock;
14use fs_management::filesystem::BlockConnector;
15#[cfg(feature = "for-testing")]
16use futures::StreamExt;
17use std::sync::Arc;
18
19#[cfg(not(feature = "for-testing"))]
20mod data;
21#[cfg(not(feature = "for-testing"))]
22use data::Data;
23
24#[cfg(feature = "for-testing")]
25mod data_for_testing;
26#[cfg(feature = "for-testing")]
27use data_for_testing::{Data, FscryptKeys};
28#[cfg(feature = "for-testing")]
29pub use data_for_testing::{Observer, WriteAction, WriteCache};
30
31pub struct VmoBackedServer {
33 server: BlockServer<SessionManager<Data>>,
34}
35
36impl VmoBackedServer {
37 pub async fn serve(&self, requests: fblock::BlockRequestStream) -> Result<(), Error> {
39 let res = self.server.handle_requests(requests).await;
40
41 #[cfg(feature = "for-testing")]
42 self.server.session_manager().interface().client_closed()?;
43
44 res
45 }
46
47 pub fn new(block_count: u64, block_size: u32, initial_content: &[u8]) -> Result<Self, Error> {
48 VmoBackedServerOptions {
49 block_size,
50 initial_contents: InitialContents::FromCapacityAndBuffer(block_count, initial_content),
51 ..Default::default()
52 }
53 .build()
54 }
55
56 pub fn from_vmo(block_size: u32, vmo: zx::Vmo) -> Result<Self, Error> {
57 VmoBackedServerOptions {
58 block_size,
59 initial_contents: InitialContents::FromVmo(vmo),
60 ..Default::default()
61 }
62 .build()
63 }
64}
65
66#[cfg(feature = "for-testing")]
67impl VmoBackedServer {
68 pub fn from_file(block_size: u32, path: &str) -> Self {
69 let contents = std::fs::read(path).expect("Failed to read file");
70 VmoBackedServerOptions {
71 block_size,
72 initial_contents: InitialContents::FromBuffer(&contents),
73 ..Default::default()
74 }
75 .build()
76 .expect("Failed to create VmoBackedServer.")
77 }
78
79 pub fn connect<R: BlockClient>(self: &Arc<Self>) -> R {
80 let (client, server) = fidl::endpoints::create_endpoints::<R::Protocol>();
81 let this = self.clone();
82 fuchsia_async::Task::spawn(async move {
83 let _ = this.serve(server.into_stream().cast_stream()).await;
84 })
85 .detach();
86 R::from_client(client)
87 }
88
89 pub fn connect_insecure_inline_encryption_server(
90 self: &Arc<Self>,
91 server: ServerEnd<DeviceMarker>,
92 uuid: [u8; 16],
93 ) -> impl Future<Output = ()> + Send {
94 let this = self.clone();
95 this.serve_insecure_inline_encryption(server.into_stream(), uuid)
96 }
97
98 pub fn evict_key_slot(&self, slot: u8) -> Result<(), zx::Status> {
100 self.server.session_manager().interface().fscrypt_keys().evict_key(slot)
101 }
102
103 fn program_key(&self, xts_key: &[u8; 64]) -> Result<u8, zx::Status> {
106 self.server.session_manager().interface().fscrypt_keys().program_key(xts_key)
107 }
108
109 pub async fn serve_insecure_inline_encryption(
110 self: Arc<Self>,
111 mut requests: DeviceRequestStream,
112 uuid: [u8; 16],
113 ) {
114 while let Some(Ok(request)) = requests.next().await {
115 match request {
116 DeviceRequest::ProgramKey { wrapped_key, data_unit_size: _, responder } => {
117 responder
118 .send(
119 self.program_key(&fscrypt::to_xts_key(&wrapped_key, uuid))
120 .map_err(zx::Status::into_raw),
121 )
122 .unwrap_or_else(|e| {
123 log::error!("failed to send ProgramKey response. error: {:?}", e);
124 });
125 }
126 DeviceRequest::DeriveRawSecret { mut wrapped_key, responder } => {
127 for b in &mut wrapped_key {
129 *b = *b >> 4 | *b << 4;
130 }
131 responder.send(Ok(&wrapped_key)).unwrap();
132 }
133 }
134 }
135 }
136}
137
138pub enum InitialContents<'a> {
140 FromCapacity(u64),
142 FromCapacityAndBuffer(u64, &'a [u8]),
145 FromBuffer(&'a [u8]),
148 FromVmo(zx::Vmo),
150}
151
152pub struct VmoBackedServerOptions<'a> {
153 pub info: DeviceInfo,
155 pub block_size: u32,
156 pub initial_contents: InitialContents<'a>,
157 #[cfg(feature = "for-testing")]
158 pub observer: Option<Box<dyn Observer>>,
159 #[cfg(feature = "for-testing")]
162 pub write_tracking: bool,
163 #[cfg(feature = "for-testing")]
166 pub max_jitter_usec: Option<u64>,
167}
168
169impl Default for VmoBackedServerOptions<'_> {
170 fn default() -> Self {
171 VmoBackedServerOptions {
172 info: DeviceInfo::Block(BlockInfo {
173 device_flags: fblock::DeviceFlag::empty(),
174 block_count: 0,
175 max_transfer_blocks: None,
176 }),
177 block_size: 512,
178 initial_contents: InitialContents::FromCapacity(0),
179 #[cfg(feature = "for-testing")]
180 observer: None,
181 #[cfg(feature = "for-testing")]
182 write_tracking: false,
183 #[cfg(feature = "for-testing")]
184 max_jitter_usec: None,
185 }
186 }
187}
188
189impl VmoBackedServerOptions<'_> {
190 pub fn build(self) -> Result<VmoBackedServer, Error> {
191 let (data, block_count) = match self.initial_contents {
192 InitialContents::FromCapacity(block_count) => {
193 (zx::Vmo::create(block_count * self.block_size as u64)?, block_count)
194 }
195 InitialContents::FromCapacityAndBuffer(block_count, buf) => {
196 let needed =
197 buf.len()
198 .checked_next_multiple_of(self.block_size as usize)
199 .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
200 / self.block_size as u64;
201 if needed > block_count {
202 return Err(anyhow!("Not enough capacity: {needed} vs {block_count}"));
203 }
204 let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
205 if !buf.is_empty() {
206 vmo.write(buf, 0)?;
207 }
208 (vmo, block_count)
209 }
210 InitialContents::FromBuffer(buf) => {
211 let block_count =
212 buf.len()
213 .checked_next_multiple_of(self.block_size as usize)
214 .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
215 / self.block_size as u64;
216 let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
217 if !buf.is_empty() {
218 vmo.write(buf, 0)?;
219 }
220 (vmo, block_count)
221 }
222 InitialContents::FromVmo(vmo) => {
223 let size = vmo.get_size()?;
224 let block_count = size / self.block_size as u64;
225 (vmo, block_count)
226 }
227 };
228
229 let info = match self.info {
230 DeviceInfo::Block(mut info) => {
231 info.block_count = block_count;
232 DeviceInfo::Block(info)
233 }
234 DeviceInfo::Partition(mut info) => {
235 info.block_range = Some(0..block_count);
236 DeviceInfo::Partition(info)
237 }
238 };
239 Ok(VmoBackedServer {
240 server: BlockServer::new(
241 self.block_size,
242 Arc::new(Data {
243 info,
244 block_size: self.block_size,
245 data,
246 #[cfg(feature = "for-testing")]
247 observer: self.observer,
248 #[cfg(feature = "for-testing")]
249 write_cache: self
250 .write_tracking
251 .then(|| fuchsia_sync::Mutex::new(WriteCache::new(self.block_size as u64))),
252 #[cfg(feature = "for-testing")]
253 max_jitter_usec: self.max_jitter_usec,
254 #[cfg(feature = "for-testing")]
255 fscrypt_keys: fuchsia_sync::Mutex::new(FscryptKeys::new()),
256 }),
257 ),
258 })
259 }
260}
261
262pub struct VmoBackedServerConnector {
264 scope: fuchsia_async::ScopeHandle,
265 server: Arc<VmoBackedServer>,
266}
267
268impl VmoBackedServerConnector {
269 pub fn new(server: Arc<VmoBackedServer>) -> Self {
271 Self { scope: fuchsia_async::Scope::current(), server }
272 }
273
274 pub fn new_with_scope(server: Arc<VmoBackedServer>, scope: fuchsia_async::ScopeHandle) -> Self {
276 Self { scope, server }
277 }
278}
279
280impl BlockConnector for VmoBackedServerConnector {
281 fn connect_channel_to_block(
282 &self,
283 server_end: ServerEnd<fblock::BlockMarker>,
284 ) -> Result<(), Error> {
285 let server = self.server.clone();
286 let _ = self.scope.spawn(async move {
287 let _ = server.serve(server_end.into_stream()).await;
288 });
289 Ok(())
290 }
291}
292
293pub trait BlockClient: FromClient {}
294
295impl BlockClient for fblock::BlockProxy {}
296impl BlockClient for fblock::BlockSynchronousProxy {}
297impl BlockClient for ClientEnd<fblock::BlockMarker> {}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use block_server::async_interface::Interface;
303 use block_server::{InlineCryptoOptions, ReadOptions, WriteOptions};
304
305 #[fuchsia::test]
306 async fn test_program_and_evict_key_slot() {
307 let block_size = 4096;
308 let server =
309 VmoBackedServer::new(100, block_size, &[]).expect("Failed to create VmoBackedServer");
310
311 let key = [0xaa; 64];
312 let slot = server.program_key(&key).expect("program_key failed");
313 assert_eq!(slot, 0);
314
315 let block_interface = server.server.session_manager().interface();
317 let vmo = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
319 let original_data = vec![0xbb; block_size as usize];
320 vmo.write(&original_data, 0).expect("Vmo::write failed");
321 let write_opts = WriteOptions {
322 inline_crypto: InlineCryptoOptions::enabled(slot, 0),
323 ..Default::default()
324 };
325 block_interface.write(0, 1, &vmo, 0, write_opts, None).await.expect("write failed");
326
327 let vmo_read = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
329 let read_opts = ReadOptions {
330 inline_crypto: InlineCryptoOptions::enabled(slot, 0),
331 ..Default::default()
332 };
333 block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await.expect("read failed");
334 let mut read_data = vec![0u8; block_size as usize];
335 vmo_read.read(&mut read_data, 0).expect("Vmo::read failed");
336 assert_eq!(read_data, original_data);
337
338 server.evict_key_slot(slot).expect("evict_key_slot failed");
339 assert_eq!(server.evict_key_slot(slot), Err(zx::Status::INVALID_ARGS));
340
341 assert_eq!(
343 block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await,
344 Err(zx::Status::IO)
345 );
346
347 assert_eq!(
348 block_interface.write(0, 1, &vmo, 0, write_opts, None).await,
349 Err(zx::Status::IO)
350 );
351 }
352
353 #[fuchsia::test]
354 async fn test_program_key_out_of_slots() {
355 let server = VmoBackedServer::new(100, 512, &[]).expect("Failed to create VmoBackedServer");
356
357 let key = [0xaa; 64];
358 for expected_slot in 0..=u8::MAX {
359 let slot = server.program_key(&key).expect("program_key failed");
360 assert_eq!(slot, expected_slot);
361 }
362 assert_eq!(server.program_key(&key), Err(zx::Status::NO_RESOURCES));
363 }
364}