blob: 54d0145ee5d041fc296202b30da9183aba0ff844 [file] [log] [blame]
Inseob Kimc0886c22021-12-13 17:41:24 +09001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Rust bindgen interface for FSVerity Metadata file (.fsv_meta)
18use authfs_fsverity_metadata_bindgen::{
Victor Hsieh5deba522022-01-10 17:18:40 -080019 fsverity_descriptor, fsverity_metadata_header, FSVERITY_HASH_ALG_SHA256,
20 FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7, FSVERITY_SIGNATURE_TYPE_RAW,
Inseob Kimc0886c22021-12-13 17:41:24 +090021};
22
Andrew Scull551e3c92022-05-23 11:04:20 +000023use openssl::sha::sha256;
Inseob Kimc0886c22021-12-13 17:41:24 +090024use std::cmp::min;
Victor Hsieh5deba522022-01-10 17:18:40 -080025use std::ffi::OsString;
26use std::fs::File;
27use std::io::{self, Read, Seek};
28use std::mem::{size_of, zeroed};
29use std::os::unix::fs::{FileExt, MetadataExt};
30use std::path::{Path, PathBuf};
31use std::slice::from_raw_parts_mut;
32
33/// Offset of `descriptor` in `struct fsverity_metadatata_header`.
34const DESCRIPTOR_OFFSET: usize = 4;
Inseob Kimc0886c22021-12-13 17:41:24 +090035
36/// Structure for parsed metadata.
37pub struct FSVerityMetadata {
38 /// Header for the metadata.
39 pub header: fsverity_metadata_header,
40
Victor Hsieh5deba522022-01-10 17:18:40 -080041 /// fs-verity digest of the file, with hash algorithm defined in the fs-verity descriptor.
42 pub digest: Vec<u8>,
43
Inseob Kimc0886c22021-12-13 17:41:24 +090044 /// Optional signature for the metadata.
45 pub signature: Option<Vec<u8>>,
46
47 metadata_file: File,
48
49 merkle_tree_offset: u64,
50}
51
52impl FSVerityMetadata {
53 /// Read the raw Merkle tree from the metadata, if it exists. The API semantics is similar to a
54 /// regular pread(2), and may not return full requested buffer.
55 pub fn read_merkle_tree(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080056 let file_size = self.metadata_file.metadata()?.size();
Inseob Kimc0886c22021-12-13 17:41:24 +090057 let start = self.merkle_tree_offset + offset;
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080058 let end = min(file_size, start + buf.len() as u64);
Inseob Kimc0886c22021-12-13 17:41:24 +090059 let read_size = (end - start) as usize;
60 debug_assert!(read_size <= buf.len());
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080061 if read_size == 0 {
62 Ok(0)
63 } else {
64 self.metadata_file.read_exact_at(&mut buf[..read_size], start)?;
65 Ok(read_size)
66 }
Inseob Kimc0886c22021-12-13 17:41:24 +090067 }
68}
69
Inseob Kimc0886c22021-12-13 17:41:24 +090070/// Common block and page size in Linux.
71pub const CHUNK_SIZE: u64 = authfs_fsverity_metadata_bindgen::CHUNK_SIZE;
72
73/// Derive a path of metadata for a given path.
74/// e.g. "system/framework/foo.jar" -> "system/framework/foo.jar.fsv_meta"
75pub fn get_fsverity_metadata_path(path: &Path) -> PathBuf {
76 let mut os_string: OsString = path.into();
77 os_string.push(".fsv_meta");
78 os_string.into()
79}
80
81/// Parse metadata from given file, and returns a structure for the metadata.
82pub fn parse_fsverity_metadata(mut metadata_file: File) -> io::Result<Box<FSVerityMetadata>> {
Victor Hsieh5deba522022-01-10 17:18:40 -080083 let (header, digest) = {
84 // SAFETY: The header doesn't include any pointers.
85 let mut header: fsverity_metadata_header = unsafe { zeroed() };
Inseob Kimc0886c22021-12-13 17:41:24 +090086
Victor Hsieh5deba522022-01-10 17:18:40 -080087 // SAFETY: fsverity_metadata_header is packed, so reading/write from/to the back_buffer
88 // won't overflow.
89 let back_buffer = unsafe {
90 from_raw_parts_mut(
91 &mut header as *mut fsverity_metadata_header as *mut u8,
92 size_of::<fsverity_metadata_header>(),
93 )
94 };
95 metadata_file.read_exact(back_buffer)?;
96
97 // Digest needs to be calculated with the raw value (without changing the endianness).
98 let digest = match header.descriptor.hash_algorithm {
Andrew Scull551e3c92022-05-23 11:04:20 +000099 FSVERITY_HASH_ALG_SHA256 => Ok(sha256(
100 &back_buffer
101 [DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + size_of::<fsverity_descriptor>()],
102 )
103 .to_vec()),
Victor Hsieh5deba522022-01-10 17:18:40 -0800104 alg => Err(io::Error::new(
105 io::ErrorKind::Other,
106 format!("Unsupported hash algorithm {}, continue (likely failing soon)", alg),
107 )),
108 }?;
Inseob Kimc0886c22021-12-13 17:41:24 +0900109
110 // TODO(inseob): This doesn't seem ideal. Maybe we can consider nom?
111 header.version = u32::from_le(header.version);
112 header.descriptor.data_size = u64::from_le(header.descriptor.data_size);
113 header.signature_type = u32::from_le(header.signature_type);
114 header.signature_size = u32::from_le(header.signature_size);
Victor Hsieh5deba522022-01-10 17:18:40 -0800115 (header, digest)
Inseob Kimc0886c22021-12-13 17:41:24 +0900116 };
117
118 if header.version != 1 {
119 return Err(io::Error::new(io::ErrorKind::Other, "unsupported metadata version"));
120 }
121
122 let signature = match header.signature_type {
123 FSVERITY_SIGNATURE_TYPE_NONE => None,
124 FSVERITY_SIGNATURE_TYPE_PKCS7 | FSVERITY_SIGNATURE_TYPE_RAW => {
125 // TODO: unpad pkcs7?
126 let mut buf = vec![0u8; header.signature_size as usize];
127 metadata_file.read_exact(&mut buf)?;
128 Some(buf)
129 }
130 _ => return Err(io::Error::new(io::ErrorKind::Other, "unknown signature type")),
131 };
132
133 // merkle tree is at the next 4K boundary
134 let merkle_tree_offset =
135 (metadata_file.stream_position()? + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
136
Victor Hsieh5deba522022-01-10 17:18:40 -0800137 Ok(Box::new(FSVerityMetadata { header, digest, signature, metadata_file, merkle_tree_offset }))
Inseob Kimc0886c22021-12-13 17:41:24 +0900138}