Skip to main content

vmo_backed_block_server/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
31/// A local server backed by a VMO.
32pub struct VmoBackedServer {
33    server: BlockServer<SessionManager<Data>>,
34}
35
36impl VmoBackedServer {
37    /// Handles `requests`.  The future will resolve when the stream terminates.
38    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    /// Evict key slot for software ciphers.
109    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    /// Implements software-fallback for fuchsia_hardware_inlineencryption.ProgramKey. There is a
114    /// maximum of 256 keyslots. Insert keyslot at the next available slot.
115    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                    responder
128                        .send(
129                            self.program_key(&fscrypt::to_xts_key(&wrapped_key, uuid))
130                                .map_err(zx::Status::into_raw),
131                        )
132                        .unwrap_or_else(|e| {
133                            log::error!("failed to send ProgramKey response. error: {:?}", e);
134                        });
135                }
136                DeviceRequest::DeriveRawSecret { mut wrapped_key, responder } => {
137                    // Swap the nibbles.
138                    for b in &mut wrapped_key {
139                        *b = *b >> 4 | *b << 4;
140                    }
141                    responder.send(Ok(&wrapped_key)).unwrap();
142                }
143            }
144        }
145    }
146}
147
148/// The initial contents of the VMO.  This also determines the size of the block device.
149pub enum InitialContents<'a> {
150    /// An empty VMO will be created with capacity for this many *blocks*.
151    FromCapacity(u64),
152    /// A VMO is created with capacity for this many *blocks* and the buffer's contents copied into
153    /// it.
154    FromCapacityAndBuffer(u64, &'a [u8]),
155    /// A VMO is created which is exactly large enough for the initial contents (rounded up to block
156    /// size), and the buffer's contents copied into it.
157    FromBuffer(&'a [u8]),
158    /// The provided VMO is used.  If its size is not block-aligned, the data will be truncated.
159    FromVmo(zx::Vmo),
160}
161
162pub struct VmoBackedServerOptions<'a> {
163    /// NB: `block_count` is ignored as that comes from `initial_contents`.
164    pub info: DeviceInfo,
165    pub block_size: u32,
166    pub initial_contents: InitialContents<'a>,
167    #[cfg(feature = "for-testing")]
168    pub observer: Option<Box<dyn Observer>>,
169    /// Enables write tracking so [`Observer::flush`] and [`Observer::barrier`] will be provided
170    /// with [`WriteCache`]. Note that this is expensive.
171    #[cfg(feature = "for-testing")]
172    pub write_tracking: bool,
173    /// If set, each operation will be delayed by a random duration <= this value, which is useful
174    /// for testing race conditions due to out-of-order block requests.
175    #[cfg(feature = "for-testing")]
176    pub max_jitter_usec: Option<u64>,
177}
178
179impl Default for VmoBackedServerOptions<'_> {
180    fn default() -> Self {
181        VmoBackedServerOptions {
182            info: DeviceInfo::Block(BlockInfo {
183                device_flags: fblock::DeviceFlag::empty(),
184                block_count: 0,
185                max_transfer_blocks: None,
186            }),
187            block_size: 512,
188            initial_contents: InitialContents::FromCapacity(0),
189            #[cfg(feature = "for-testing")]
190            observer: None,
191            #[cfg(feature = "for-testing")]
192            write_tracking: false,
193            #[cfg(feature = "for-testing")]
194            max_jitter_usec: None,
195        }
196    }
197}
198
199impl VmoBackedServerOptions<'_> {
200    pub fn build(self) -> Result<VmoBackedServer, Error> {
201        let (data, block_count) = match self.initial_contents {
202            InitialContents::FromCapacity(block_count) => {
203                (zx::Vmo::create(block_count * self.block_size as u64)?, block_count)
204            }
205            InitialContents::FromCapacityAndBuffer(block_count, buf) => {
206                let needed =
207                    buf.len()
208                        .checked_next_multiple_of(self.block_size as usize)
209                        .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
210                        / self.block_size as u64;
211                if needed > block_count {
212                    return Err(anyhow!("Not enough capacity: {needed} vs {block_count}"));
213                }
214                let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
215                if !buf.is_empty() {
216                    vmo.write(buf, 0)?;
217                }
218                (vmo, block_count)
219            }
220            InitialContents::FromBuffer(buf) => {
221                let block_count =
222                    buf.len()
223                        .checked_next_multiple_of(self.block_size as usize)
224                        .ok_or_else(|| anyhow!("Invalid buffer size"))? as u64
225                        / self.block_size as u64;
226                let vmo = zx::Vmo::create(block_count * self.block_size as u64)?;
227                if !buf.is_empty() {
228                    vmo.write(buf, 0)?;
229                }
230                (vmo, block_count)
231            }
232            InitialContents::FromVmo(vmo) => {
233                let size = vmo.get_size()?;
234                let block_count = size / self.block_size as u64;
235                (vmo, block_count)
236            }
237        };
238
239        let info = match self.info {
240            DeviceInfo::Block(mut info) => {
241                info.block_count = block_count;
242                DeviceInfo::Block(info)
243            }
244            DeviceInfo::Partition(mut info) => {
245                info.block_count = block_count;
246                DeviceInfo::Partition(info)
247            }
248            DeviceInfo::Volume(info) => DeviceInfo::Volume(info),
249        };
250        Ok(VmoBackedServer {
251            server: BlockServer::new(
252                self.block_size,
253                Arc::new(Data {
254                    info,
255                    block_size: self.block_size,
256                    data,
257                    #[cfg(feature = "for-testing")]
258                    observer: self.observer,
259                    #[cfg(feature = "for-testing")]
260                    write_cache: self
261                        .write_tracking
262                        .then(|| fuchsia_sync::Mutex::new(WriteCache::new(self.block_size as u64))),
263                    #[cfg(feature = "for-testing")]
264                    max_jitter_usec: self.max_jitter_usec,
265                    #[cfg(feature = "for-testing")]
266                    fscrypt_keys: fuchsia_sync::Mutex::new(FscryptKeys::new()),
267                }),
268            ),
269        })
270    }
271}
272
273/// Implements `BlockConnector` to vend connections to a VmoBackedServer.
274pub struct VmoBackedServerConnector {
275    scope: fuchsia_async::ScopeHandle,
276    server: Arc<VmoBackedServer>,
277}
278
279impl VmoBackedServerConnector {
280    /// New connections will served on the current scope.
281    pub fn new(server: Arc<VmoBackedServer>) -> Self {
282        Self { scope: fuchsia_async::Scope::current(), server }
283    }
284
285    /// New connections will served on the provided scope.
286    pub fn new_with_scope(server: Arc<VmoBackedServer>, scope: fuchsia_async::ScopeHandle) -> Self {
287        Self { scope, server }
288    }
289}
290
291impl BlockConnector for VmoBackedServerConnector {
292    fn connect_channel_to_block(
293        &self,
294        server_end: ServerEnd<fblock::BlockMarker>,
295    ) -> Result<(), Error> {
296        let server = self.server.clone();
297        let _ = self.scope.spawn(async move {
298            let _ = server.serve(server_end.into_stream()).await;
299        });
300        Ok(())
301    }
302}
303
304pub trait BlockClient: FromClient {}
305
306impl BlockClient for fblock::BlockProxy {}
307impl BlockClient for fblock::BlockSynchronousProxy {}
308impl BlockClient for ClientEnd<fblock::BlockMarker> {}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use block_server::async_interface::Interface;
314    use block_server::{InlineCryptoOptions, ReadOptions, WriteOptions};
315
316    #[fuchsia::test]
317    async fn test_program_and_evict_key_slot() {
318        let block_size = 4096;
319        let server =
320            VmoBackedServer::new(100, block_size, &[]).expect("Failed to create VmoBackedServer");
321
322        let key = [0xaa; 64];
323        let slot = server.program_key(&key).expect("program_key failed");
324        assert_eq!(slot, 0);
325
326        // Use the internal interface to avoid FIDL complexity for this test.
327        let block_interface = server.server.session_manager().interface();
328        // Verify that we can write and read using the programmed key.
329        let vmo = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
330        let original_data = vec![0xbb; block_size as usize];
331        vmo.write(&original_data, 0).expect("Vmo::write failed");
332        let write_opts = WriteOptions {
333            inline_crypto: InlineCryptoOptions::enabled(slot, 0),
334            ..Default::default()
335        };
336        block_interface.write(0, 1, &vmo, 0, write_opts, None).await.expect("write failed");
337
338        // Verify we can read it back.
339        let vmo_read = Arc::new(zx::Vmo::create(block_size as u64).expect("Vmo::create failed"));
340        let read_opts = ReadOptions {
341            inline_crypto: InlineCryptoOptions::enabled(slot, 0),
342            ..Default::default()
343        };
344        block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await.expect("read failed");
345        let mut read_data = vec![0u8; block_size as usize];
346        vmo_read.read(&mut read_data, 0).expect("Vmo::read failed");
347        assert_eq!(read_data, original_data);
348
349        server.evict_key_slot(slot).expect("evict_key_slot failed");
350        assert_eq!(server.evict_key_slot(slot), Err(zx::Status::INVALID_ARGS));
351
352        // Writing and reading from file after the key has been evicted should fail.
353        assert_eq!(
354            block_interface.read(0, 1, &vmo_read, 0, read_opts, None).await,
355            Err(zx::Status::IO)
356        );
357
358        assert_eq!(
359            block_interface.write(0, 1, &vmo, 0, write_opts, None).await,
360            Err(zx::Status::IO)
361        );
362    }
363
364    #[fuchsia::test]
365    async fn test_program_key_out_of_slots() {
366        let server = VmoBackedServer::new(100, 512, &[]).expect("Failed to create VmoBackedServer");
367
368        let key = [0xaa; 64];
369        for expected_slot in 0..=u8::MAX {
370            let slot = server.program_key(&key).expect("program_key failed");
371            assert_eq!(slot, expected_slot);
372        }
373        assert_eq!(server.program_key(&key), Err(zx::Status::NO_RESOURCES));
374    }
375}