blob: 17a0a2af72672d763b8d0b7b68c14b72fdf8e20c [file] [log] [blame]
Victor Hsiehdde17902021-02-26 12:35:31 -08001/*
2 * Copyright (C) 2020 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
17use libc::EIO;
18use std::io;
19
20use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError};
21use super::sys::{FS_VERITY_HASH_ALG_SHA256, FS_VERITY_MAGIC};
22use crate::auth::Authenticator;
23use crate::common::{divide_roundup, CHUNK_SIZE};
24use crate::crypto::{CryptoError, Sha256Hasher};
Victor Hsiehd0bb5d32021-03-19 12:48:03 -070025use crate::file::{ChunkBuffer, ReadByChunk};
Victor Hsiehdde17902021-02-26 12:35:31 -080026
27const ZEROS: [u8; CHUNK_SIZE as usize] = [0u8; CHUNK_SIZE as usize];
28
29// The size of `struct fsverity_formatted_digest` in Linux with SHA-256.
30const SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256: usize = 12 + Sha256Hasher::HASH_SIZE;
31
32type HashBuffer = [u8; Sha256Hasher::HASH_SIZE];
33
34fn hash_with_padding(chunk: &[u8], pad_to: usize) -> Result<HashBuffer, CryptoError> {
35 let padding_size = pad_to - chunk.len();
Chris Wailes68c39f82021-07-27 16:03:44 -070036 Sha256Hasher::new()?.update(chunk)?.update(&ZEROS[..padding_size])?.finalize()
Victor Hsiehdde17902021-02-26 12:35:31 -080037}
38
Victor Hsiehd0bb5d32021-03-19 12:48:03 -070039fn verity_check<T: ReadByChunk>(
Victor Hsiehdde17902021-02-26 12:35:31 -080040 chunk: &[u8],
41 chunk_index: u64,
42 file_size: u64,
43 merkle_tree: &T,
44) -> Result<HashBuffer, FsverityError> {
45 // The caller should not be able to produce a chunk at the first place if `file_size` is 0. The
Victor Hsiehd0bb5d32021-03-19 12:48:03 -070046 // current implementation expects to crash when a `ReadByChunk` implementation reads
Victor Hsiehdde17902021-02-26 12:35:31 -080047 // beyond the file size, including empty file.
48 assert_ne!(file_size, 0);
49
Chris Wailes68c39f82021-07-27 16:03:44 -070050 let chunk_hash = hash_with_padding(chunk, CHUNK_SIZE as usize)?;
Victor Hsiehdde17902021-02-26 12:35:31 -080051
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080052 // When the file is smaller or equal to CHUNK_SIZE, the root of Merkle tree is defined as the
53 // hash of the file content, plus padding.
54 if file_size <= CHUNK_SIZE {
55 return Ok(chunk_hash);
56 }
57
Victor Hsiehdde17902021-02-26 12:35:31 -080058 fsverity_walk(chunk_index, file_size, merkle_tree)?.try_fold(
59 chunk_hash,
60 |actual_hash, result| {
61 let (merkle_chunk, hash_offset_in_chunk) = result?;
62 let expected_hash =
63 &merkle_chunk[hash_offset_in_chunk..hash_offset_in_chunk + Sha256Hasher::HASH_SIZE];
64 if actual_hash != expected_hash {
65 return Err(FsverityError::CannotVerify);
66 }
67 Ok(hash_with_padding(&merkle_chunk, CHUNK_SIZE as usize)?)
68 },
69 )
70}
71
72/// Given a chunk index and the size of the file, returns an iterator that walks the Merkle tree
73/// from the leaf to the root. The iterator carries the slice of the chunk/node as well as the
74/// offset of the child node's hash. It is up to the iterator user to use the node and hash,
75/// e.g. for the actual verification.
76#[allow(clippy::needless_collect)]
Victor Hsiehd0bb5d32021-03-19 12:48:03 -070077fn fsverity_walk<T: ReadByChunk>(
Victor Hsiehdde17902021-02-26 12:35:31 -080078 chunk_index: u64,
79 file_size: u64,
80 merkle_tree: &T,
81) -> Result<impl Iterator<Item = Result<([u8; 4096], usize), FsverityError>> + '_, FsverityError> {
82 let hashes_per_node = CHUNK_SIZE / Sha256Hasher::HASH_SIZE as u64;
83 debug_assert_eq!(hashes_per_node, 128u64);
84 let max_level = merkle_tree_height(file_size).expect("file should not be empty") as u32;
85 let root_to_leaf_steps = (0..=max_level)
86 .rev()
87 .map(|x| {
88 let leaves_per_hash = hashes_per_node.pow(x);
89 let leaves_size_per_hash = CHUNK_SIZE * leaves_per_hash;
90 let leaves_size_per_node = leaves_size_per_hash * hashes_per_node;
91 let nodes_at_level = divide_roundup(file_size, leaves_size_per_node);
92 let level_size = nodes_at_level * CHUNK_SIZE;
93 let offset_in_level = (chunk_index / leaves_per_hash) * Sha256Hasher::HASH_SIZE as u64;
94 (level_size, offset_in_level)
95 })
96 .scan(0, |level_offset, (level_size, offset_in_level)| {
97 let this_level_offset = *level_offset;
98 *level_offset += level_size;
99 let global_hash_offset = this_level_offset + offset_in_level;
100 Some(global_hash_offset)
101 })
102 .map(|global_hash_offset| {
103 let chunk_index = global_hash_offset / CHUNK_SIZE;
104 let hash_offset_in_chunk = (global_hash_offset % CHUNK_SIZE) as usize;
105 (chunk_index, hash_offset_in_chunk)
106 })
107 .collect::<Vec<_>>(); // Needs to collect first to be able to reverse below.
108
109 Ok(root_to_leaf_steps.into_iter().rev().map(move |(chunk_index, hash_offset_in_chunk)| {
110 let mut merkle_chunk = [0u8; 4096];
111 // read_chunk is supposed to return a full chunk, or an incomplete one at the end of the
112 // file. In the incomplete case, the hash is calculated with 0-padding to the chunk size.
113 // Therefore, we don't need to check the returned size here.
114 let _ = merkle_tree.read_chunk(chunk_index, &mut merkle_chunk)?;
115 Ok((merkle_chunk, hash_offset_in_chunk))
116 }))
117}
118
119fn build_fsverity_formatted_digest(
120 root_hash: &HashBuffer,
121 file_size: u64,
122) -> Result<[u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256], CryptoError> {
123 let digest = build_fsverity_digest(root_hash, file_size)?;
124 // Little-endian byte representation of fsverity_formatted_digest from linux/fsverity.h
125 // Not FFI-ed as it seems easier to deal with the raw bytes manually.
126 let mut formatted_digest = [0u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256];
127 formatted_digest[0..8].copy_from_slice(FS_VERITY_MAGIC);
128 formatted_digest[8..10].copy_from_slice(&(FS_VERITY_HASH_ALG_SHA256 as u16).to_le_bytes());
129 formatted_digest[10..12].copy_from_slice(&(Sha256Hasher::HASH_SIZE as u16).to_le_bytes());
130 formatted_digest[12..].copy_from_slice(&digest);
131 Ok(formatted_digest)
132}
133
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700134pub struct VerifiedFileReader<F: ReadByChunk, M: ReadByChunk> {
Victor Hsiehdde17902021-02-26 12:35:31 -0800135 chunked_file: F,
136 file_size: u64,
137 merkle_tree: M,
138 root_hash: HashBuffer,
139}
140
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700141impl<F: ReadByChunk, M: ReadByChunk> VerifiedFileReader<F, M> {
Victor Hsiehdde17902021-02-26 12:35:31 -0800142 pub fn new<A: Authenticator>(
143 authenticator: &A,
144 chunked_file: F,
145 file_size: u64,
Inseob Kimc0886c22021-12-13 17:41:24 +0900146 sig: Option<&[u8]>,
Victor Hsiehdde17902021-02-26 12:35:31 -0800147 merkle_tree: M,
Victor Hsieh09e26262021-03-03 16:00:55 -0800148 ) -> Result<VerifiedFileReader<F, M>, FsverityError> {
Victor Hsiehdde17902021-02-26 12:35:31 -0800149 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh35dfa1e2022-01-12 17:03:35 -0800150 if file_size <= CHUNK_SIZE {
151 let _size = chunked_file.read_chunk(0, &mut buf)?;
152 // The rest of buffer is 0-padded.
153 } else {
154 let size = merkle_tree.read_chunk(0, &mut buf)?;
155 if buf.len() != size {
156 return Err(FsverityError::InsufficientData(size));
157 }
Victor Hsiehdde17902021-02-26 12:35:31 -0800158 }
159 let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
160 let formatted_digest = build_fsverity_formatted_digest(&root_hash, file_size)?;
Inseob Kimc0886c22021-12-13 17:41:24 +0900161 let valid = authenticator.verify(sig, &formatted_digest)?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800162 if valid {
Victor Hsieh09e26262021-03-03 16:00:55 -0800163 Ok(VerifiedFileReader { chunked_file, file_size, merkle_tree, root_hash })
Victor Hsiehdde17902021-02-26 12:35:31 -0800164 } else {
165 Err(FsverityError::BadSignature)
166 }
167 }
168}
169
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700170impl<F: ReadByChunk, M: ReadByChunk> ReadByChunk for VerifiedFileReader<F, M> {
171 fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
Victor Hsiehdde17902021-02-26 12:35:31 -0800172 let size = self.chunked_file.read_chunk(chunk_index, buf)?;
173 let root_hash = verity_check(&buf[..size], chunk_index, self.file_size, &self.merkle_tree)
174 .map_err(|_| io::Error::from_raw_os_error(EIO))?;
175 if root_hash != self.root_hash {
176 Err(io::Error::from_raw_os_error(EIO))
177 } else {
178 Ok(size)
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::auth::FakeAuthenticator;
Victor Hsieh88e50172021-10-15 13:27:13 -0700187 use crate::file::ReadByChunk;
Victor Hsiehdde17902021-02-26 12:35:31 -0800188 use anyhow::Result;
Inseob Kimc0886c22021-12-13 17:41:24 +0900189 use authfs_fsverity_metadata::{parse_fsverity_metadata, FSVerityMetadata};
Victor Hsieh88e50172021-10-15 13:27:13 -0700190 use std::cmp::min;
Inseob Kimc0886c22021-12-13 17:41:24 +0900191 use std::fs::File;
Victor Hsieh88e50172021-10-15 13:27:13 -0700192 use std::os::unix::fs::FileExt;
193
194 struct LocalFileReader {
195 file: File,
196 size: u64,
197 }
198
199 impl LocalFileReader {
200 fn new(file: File) -> io::Result<LocalFileReader> {
201 let size = file.metadata()?.len();
202 Ok(LocalFileReader { file, size })
203 }
204
205 fn len(&self) -> u64 {
206 self.size
207 }
208 }
209
210 impl ReadByChunk for LocalFileReader {
211 fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
212 let start = chunk_index * CHUNK_SIZE;
213 if start >= self.size {
214 return Ok(0);
215 }
216 let end = min(self.size, start + CHUNK_SIZE);
217 let read_size = (end - start) as usize;
218 debug_assert!(read_size <= buf.len());
219 self.file.read_exact_at(&mut buf[..read_size], start)?;
220 Ok(read_size)
221 }
222 }
Victor Hsiehdde17902021-02-26 12:35:31 -0800223
Inseob Kimc0886c22021-12-13 17:41:24 +0900224 type LocalVerifiedFileReader = VerifiedFileReader<LocalFileReader, MerkleTreeReader>;
225
226 pub struct MerkleTreeReader {
227 metadata: Box<FSVerityMetadata>,
228 }
229
230 impl ReadByChunk for MerkleTreeReader {
231 fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
232 self.metadata.read_merkle_tree(chunk_index * CHUNK_SIZE, buf)
233 }
234 }
Victor Hsiehdde17902021-02-26 12:35:31 -0800235
236 fn total_chunk_number(file_size: u64) -> u64 {
237 (file_size + 4095) / 4096
238 }
239
240 // Returns a reader with fs-verity verification and the file size.
241 fn new_reader_with_fsverity(
242 content_path: &str,
Inseob Kimc0886c22021-12-13 17:41:24 +0900243 metadata_path: &str,
Victor Hsieh09e26262021-03-03 16:00:55 -0800244 ) -> Result<(LocalVerifiedFileReader, u64)> {
245 let file_reader = LocalFileReader::new(File::open(content_path)?)?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800246 let file_size = file_reader.len();
Inseob Kimc0886c22021-12-13 17:41:24 +0900247 let metadata = parse_fsverity_metadata(File::open(metadata_path)?)?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800248 let authenticator = FakeAuthenticator::always_succeed();
249 Ok((
Inseob Kimc0886c22021-12-13 17:41:24 +0900250 VerifiedFileReader::new(
251 &authenticator,
252 file_reader,
253 file_size,
254 metadata.signature.clone().as_deref(),
255 MerkleTreeReader { metadata },
256 )?,
Victor Hsiehdde17902021-02-26 12:35:31 -0800257 file_size,
258 ))
259 }
260
261 #[test]
262 fn fsverity_verify_full_read_4k() -> Result<()> {
Inseob Kimc0886c22021-12-13 17:41:24 +0900263 let (file_reader, file_size) =
264 new_reader_with_fsverity("testdata/input.4k", "testdata/input.4k.fsv_meta")?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800265
266 for i in 0..total_chunk_number(file_size) {
267 let mut buf = [0u8; 4096];
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700268 assert!(file_reader.read_chunk(i, &mut buf).is_ok());
Victor Hsiehdde17902021-02-26 12:35:31 -0800269 }
270 Ok(())
271 }
272
273 #[test]
274 fn fsverity_verify_full_read_4k1() -> Result<()> {
Inseob Kimc0886c22021-12-13 17:41:24 +0900275 let (file_reader, file_size) =
276 new_reader_with_fsverity("testdata/input.4k1", "testdata/input.4k1.fsv_meta")?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800277
278 for i in 0..total_chunk_number(file_size) {
279 let mut buf = [0u8; 4096];
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700280 assert!(file_reader.read_chunk(i, &mut buf).is_ok());
Victor Hsiehdde17902021-02-26 12:35:31 -0800281 }
282 Ok(())
283 }
284
285 #[test]
286 fn fsverity_verify_full_read_4m() -> Result<()> {
Inseob Kimc0886c22021-12-13 17:41:24 +0900287 let (file_reader, file_size) =
288 new_reader_with_fsverity("testdata/input.4m", "testdata/input.4m.fsv_meta")?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800289
290 for i in 0..total_chunk_number(file_size) {
291 let mut buf = [0u8; 4096];
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700292 assert!(file_reader.read_chunk(i, &mut buf).is_ok());
Victor Hsiehdde17902021-02-26 12:35:31 -0800293 }
294 Ok(())
295 }
296
297 #[test]
298 fn fsverity_verify_bad_merkle_tree() -> Result<()> {
299 let (file_reader, _) = new_reader_with_fsverity(
300 "testdata/input.4m",
Inseob Kimc0886c22021-12-13 17:41:24 +0900301 "testdata/input.4m.fsv_meta.bad_merkle", // First leaf node is corrupted.
Victor Hsiehdde17902021-02-26 12:35:31 -0800302 )?;
303
304 // A lowest broken node (a 4K chunk that contains 128 sha256 hashes) will fail the read
305 // failure of the underlying chunks, but not before or after.
306 let mut buf = [0u8; 4096];
307 let num_hashes = 4096 / 32;
308 let last_index = num_hashes;
309 for i in 0..last_index {
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700310 assert!(file_reader.read_chunk(i, &mut buf).is_err());
Victor Hsiehdde17902021-02-26 12:35:31 -0800311 }
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700312 assert!(file_reader.read_chunk(last_index, &mut buf).is_ok());
Victor Hsiehdde17902021-02-26 12:35:31 -0800313 Ok(())
314 }
315
316 #[test]
317 fn invalid_signature() -> Result<()> {
318 let authenticator = FakeAuthenticator::always_fail();
Victor Hsieh09e26262021-03-03 16:00:55 -0800319 let file_reader = LocalFileReader::new(File::open("testdata/input.4m")?)?;
Victor Hsiehdde17902021-02-26 12:35:31 -0800320 let file_size = file_reader.len();
Inseob Kimc0886c22021-12-13 17:41:24 +0900321 let metadata = parse_fsverity_metadata(File::open("testdata/input.4m.fsv_meta")?)?;
322 assert!(VerifiedFileReader::new(
323 &authenticator,
324 file_reader,
325 file_size,
326 metadata.signature.clone().as_deref(),
327 MerkleTreeReader { metadata },
328 )
329 .is_err());
Victor Hsiehdde17902021-02-26 12:35:31 -0800330 Ok(())
331 }
332}