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_mapper(self: &Arc<Self>) -> fblock::MapperProxy {
90 let (proxy, server) = fidl::endpoints::create_proxy::<fblock::MapperMarker>();
91 let this = self.clone();
92 fuchsia_async::Task::spawn(async move {
93 let _ = this.server.handle_mapper_requests(server.into_stream()).await;
94 })
95 .detach();
96 proxy
97 }
98
99 pub fn connect_insecure_inline_encryption_server(
100 self: &Arc<Self>,
101 server: ServerEnd<DeviceMarker>,
102 uuid: [u8; 16],
103 ) -> impl Future<Output = ()> + Send {
104 let this = self.clone();
105 this.serve_insecure_inline_encryption(server.into_stream(), uuid)
106 }
107
108 pub fn evict_key_slot(&self, slot: u8) -> Result<(), zx::Status> {
110 self.server.session_manager().interface().fscrypt_keys().evict_key(slot)
111 }
112
113 fn program_key(&self, xts_key: &[u8; 64]) -> Result<u8, zx::Status> {
116 self.server.session_manager().interface().fscrypt_keys().program_key(xts_key)
117 }
118
119 pub async fn serve_insecure_inline_encryption(
120 self: Arc<Self>,
121 mut requests: DeviceRequestStream,
122 uuid: [u8; 16],
123 ) {
124 while let Some(Ok(request)) = requests.next().await {
125 match request {
126 DeviceRequest::ProgramKey { wrapped_key, data_unit_size: _, responder } => {
127 let res = self
128 .program_key(&fscrypt::to_xts_key(&wrapped_key, uuid))
129 .and_then(|hw_slot| self.server.register_key_slot(hw_slot));
130 responder.send(res.map_err(zx::Status::into_raw)).unwrap_or_else(|e| {
131 log::error!("failed to send ProgramKey response. error: {:?}", e);
132 });
133 }
134 DeviceRequest::DeriveRawSecret { mut wrapped_key, responder } => {
135 for b in &mut wrapped_key {
137 *b = *b >> 4 | *b << 4;
138 }
139 responder.send(Ok(&wrapped_key)).unwrap();
140 }
141 }
142 }
143 }
144}
145
146pub enum InitialContents<'a> {
148 FromCapacity(u64),
150 FromCapacityAndBuffer(u64, &'a [u8]),
153 FromBuffer(&'a [u8]),
156 FromVmo(zx::Vmo),
158}
159
160pub struct VmoBackedServerOptions<'a> {
161 pub info: DeviceInfo,
163 pub block_size: u32,
164 pub initial_contents: InitialContents<'a>,
165 #[cfg(feature = "for-testing")]
166 pub observer: Option<Box<dyn Observer>>,
167 #[cfg(feature = "for-testing")]
170 pub write_tracking: bool,
171 #[cfg(feature = "for-testing")]
174 pub max_jitter_usec: Option<u64>,
175}
176
177impl Default for VmoBackedServerOptions<'_> {
178 fn default() -> Self {
179 VmoBackedServerOptions {
180 info: DeviceInfo::Block(BlockInfo {
181 device_flags: fblock::DeviceFlag::empty(),
182 block_count: 0,
183 max_transfer_blocks: None,
184 }),
185 block_size: 512,
186 initial_contents: InitialContents::FromCapacity(0),
187 #[cfg(feature = "for-testing")]
188 observer: None,
189 #[cfg(feature = "for-testing")]
190 write_tracking: false,
191 #[cfg(feature = "for-testing")]
192 max_jitter_usec: None,
193 }
194 }
195}
196
197impl VmoBackedServerOptions<'_> {
198 pub fn build(self) -> Result<VmoBackedServer, Error> {
199 let (data, block_count) = match self.initial_contents {
200 InitialContents::FromCapacity(block_count) => {
201 (zx::Vmo::create(block_count * self.block_size as u64)?, block_count)
202 }
203 InitialContents::FromCapacityAndBuffer(block_count, buf) => {
204 let needed =
205 buf.len()
206 .checked_next_multiple_of(self.block_size as usize)
207 .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
208 / self.block_size as u64;
209 if needed > block_count {
210 return Err(anyhow!("Not enough capacity: {needed} vs {block_count}"));
211 }
212 let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
213 if !buf.is_empty() {
214 vmo.write(buf, 0)?;
215 }
216 (vmo, block_count)
217 }
218 InitialContents::FromBuffer(buf) => {
219 let block_count =
220 buf.len()
221 .checked_next_multiple_of(self.block_size as usize)
222 .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
223 / self.block_size as u64;
224 let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
225 if !buf.is_empty() {
226 vmo.write(buf, 0)?;
227 }
228 (vmo, block_count)
229 }
230 InitialContents::FromVmo(vmo) => {
231 let size = vmo.get_size()?;
232 let block_count = size / self.block_size as u64;
233 (vmo, block_count)
234 }
235 };
236
237 let info = match self.info {
238 DeviceInfo::Block(mut info) => {
239 info.block_count = block_count;
240 DeviceInfo::Block(info)
241 }
242 DeviceInfo::Partition(mut info) => {
243 info.block_count = block_count;
244 DeviceInfo::Partition(info)
245 }
246 DeviceInfo::Volume(info) => DeviceInfo::Volume(info),
247 };
248 Ok(VmoBackedServer {
249 server: BlockServer::new(
250 self.block_size,
251 Arc::new(Data {
252 info,
253 block_size: self.block_size,
254 data,
255 #[cfg(feature = "for-testing")]
256 observer: self.observer,
257 #[cfg(feature = "for-testing")]
258 write_cache: self
259 .write_tracking
260 .then(|| fuchsia_sync::Mutex::new(WriteCache::new(self.block_size as u64))),
261 #[cfg(feature = "for-testing")]
262 max_jitter_usec: self.max_jitter_usec,
263 #[cfg(feature = "for-testing")]
264 fscrypt_keys: fuchsia_sync::Mutex::new(FscryptKeys::new()),
265 }),
266 ),
267 })
268 }
269}
270
271pub struct VmoBackedServerConnector {
273 scope: fuchsia_async::ScopeHandle,
274 server: Arc<VmoBackedServer>,
275}
276
277impl VmoBackedServerConnector {
278 pub fn new(server: Arc<VmoBackedServer>) -> Self {
280 Self { scope: fuchsia_async::Scope::current(), server }
281 }
282
283 pub fn new_with_scope(server: Arc<VmoBackedServer>, scope: fuchsia_async::ScopeHandle) -> Self {
285 Self { scope, server }
286 }
287}
288
289impl BlockConnector for VmoBackedServerConnector {
290 fn connect_channel_to_block(
291 &self,
292 server_end: ServerEnd<fblock::BlockMarker>,
293 ) -> Result<(), Error> {
294 let server = self.server.clone();
295 let _ = self.scope.spawn(async move {
296 let _ = server.serve(server_end.into_stream()).await;
297 });
298 Ok(())
299 }
300}
301
302pub trait BlockClient: FromClient {}
303
304impl BlockClient for fblock::BlockProxy {}
305impl BlockClient for fblock::BlockSynchronousProxy {}
306impl BlockClient for ClientEnd<fblock::BlockMarker> {}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use block_server::async_interface::Interface;
312 use block_server::{InlineCryptoOptions, ReadOptions, WriteOptions};
313
314 #[fuchsia::test]
315 async fn test_program_and_evict_key_slot() {
316 let block_size = 4096;
317 let server =
318 VmoBackedServer::new(100, block_size, &[]).expect("Failed to create VmoBackedServer");
319
320 let key = [0xaa; 64];
321 let slot = server.program_key(&key).expect("program_key failed");
322 assert_eq!(slot, 0);
323
324 let block_interface = server.server.session_manager().interface();
326 let vmo = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
328 let original_data = vec![0xbb; block_size as usize];
329 vmo.write(&original_data, 0).expect("Vmo::write failed");
330 let write_opts = WriteOptions {
331 inline_crypto: InlineCryptoOptions::enabled(slot, 0),
332 ..Default::default()
333 };
334 block_interface.write(0, 1, &vmo, 0, write_opts, None).await.expect("write failed");
335
336 let vmo_read = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
338 let read_opts = ReadOptions {
339 inline_crypto: InlineCryptoOptions::enabled(slot, 0),
340 ..Default::default()
341 };
342 block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await.expect("read failed");
343 let mut read_data = vec![0u8; block_size as usize];
344 vmo_read.read(&mut read_data, 0).expect("Vmo::read failed");
345 assert_eq!(read_data, original_data);
346
347 server.evict_key_slot(slot).expect("evict_key_slot failed");
348 assert_eq!(server.evict_key_slot(slot), Err(zx::Status::INVALID_ARGS));
349
350 assert_eq!(
352 block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await,
353 Err(zx::Status::IO)
354 );
355
356 assert_eq!(
357 block_interface.write(0, 1, &vmo, 0, write_opts, None).await,
358 Err(zx::Status::IO)
359 );
360 }
361
362 #[fuchsia::test]
363 async fn test_program_key_out_of_slots() {
364 let server = VmoBackedServer::new(100, 512, &[]).expect("Failed to create VmoBackedServer");
365
366 let key = [0xaa; 64];
367 for expected_slot in 0..=u8::MAX {
368 let slot = server.program_key(&key).expect("program_key failed");
369 assert_eq!(slot, expected_slot);
370 }
371 assert_eq!(server.program_key(&key), Err(zx::Status::NO_RESOURCES));
372 }
373}