Use fsverity metadata file for authfs
Instead of alternative signature and merkle tree, authfs will use
.fsv_meta files, which are installed to the system partition with
PRODUCT_SYSTEM_FSVERITY_GENERATE_METADATA Makefile.
Bug: 205987437
Test: atest ComposHostTestCases AuthFsTestCase
Test: run "atest ." inside authfs/tests
Change-Id: Ia9db78663e0e322c7a59305c67ac5b84716d8efe
diff --git a/authfs/src/auth.rs b/authfs/src/auth.rs
index 71ad858..729b4d2 100644
--- a/authfs/src/auth.rs
+++ b/authfs/src/auth.rs
@@ -20,7 +20,7 @@
// verify the signature, not the full certificate chain.
pub trait Authenticator {
- fn verify(&self, signature: &[u8], signed_data: &[u8]) -> io::Result<bool>;
+ fn verify(&self, signature: Option<&[u8]>, signed_data: &[u8]) -> io::Result<bool>;
}
pub struct FakeAuthenticator {
@@ -39,7 +39,7 @@
}
impl Authenticator for FakeAuthenticator {
- fn verify(&self, _signature_pem: &[u8], _signed_data: &[u8]) -> io::Result<bool> {
+ fn verify(&self, _signature_pem: Option<&[u8]>, _signed_data: &[u8]) -> io::Result<bool> {
Ok(self.should_allow)
}
}
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
new file mode 100644
index 0000000..39ce515
--- /dev/null
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -0,0 +1,20 @@
+rust_bindgen {
+ name: "libauthfs_fsverity_metadata_bindgen",
+ wrapper_src: "metadata.hpp",
+ crate_name: "authfs_fsverity_metadata_bindgen",
+ source_stem: "metadata_bindings",
+ apex_available: ["com.android.virt"],
+}
+
+rust_library {
+ name: "libauthfs_fsverity_metadata",
+ crate_name: "authfs_fsverity_metadata",
+ srcs: [
+ "metadata.rs",
+ ],
+ rustlibs: [
+ "libauthfs_fsverity_metadata_bindgen",
+ ],
+ edition: "2018",
+ apex_available: ["com.android.virt"],
+}
diff --git a/authfs/src/fsverity/metadata/metadata.hpp b/authfs/src/fsverity/metadata/metadata.hpp
new file mode 100644
index 0000000..7bbd3da
--- /dev/null
+++ b/authfs/src/fsverity/metadata/metadata.hpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AUTHFS_FSVERITY_METADATA_H
+#define AUTHFS_FSVERITY_METADATA_H
+
+// This file contains the format of fs-verity metadata (.fsv_meta).
+//
+// The header format of .fsv_meta is:
+//
+// +-----------+---------------------------------------------+------------+
+// | Address | Description | Size |
+// +-----------+---------------------------------------------+------------+
+// | 0x0000 | 32-bit LE, version of the format | 4 |
+// | | | |
+// | 0x0004 | fsverity_descriptor (see linux/fsverity.h) | 256 |
+// | | | |
+// | 0x0104 | 32-bit LE, type of signature | 4 |
+// | | (0: NONE, 1: PKCS7, 2: RAW) | |
+// | | | |
+// | 0x0108 | 32-bit LE, size of signature | 4 |
+// | | | |
+// | 0x010C | signature | See 0x0108 |
+// +-----------+---------------------------------------------+------------+
+//
+// After the header, merkle tree dump exists at the first 4K boundary. Usually it's 0x1000, but it
+// could be, for example, 0x2000 or 0x3000, depending on the size of header.
+//
+// TODO(b/193113326): sync with build/make/tools/releasetools/fsverity_metadata_generator.py
+
+#include <stddef.h>
+#include <stdint.h>
+#include <linux/fsverity.h>
+
+const uint64_t CHUNK_SIZE = 4096;
+
+enum class FSVERITY_SIGNATURE_TYPE : __le32 {
+ NONE = 0,
+ PKCS7 = 1,
+ RAW = 2,
+};
+
+struct fsverity_metadata_header {
+ __le32 version;
+ fsverity_descriptor descriptor;
+ FSVERITY_SIGNATURE_TYPE signature_type;
+ __le32 signature_size;
+} __attribute__((packed));
+
+#endif // AUTHFS_FSVERITY_METADATA_H
diff --git a/authfs/src/fsverity/metadata/metadata.rs b/authfs/src/fsverity/metadata/metadata.rs
new file mode 100644
index 0000000..0092bee
--- /dev/null
+++ b/authfs/src/fsverity/metadata/metadata.rs
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Rust bindgen interface for FSVerity Metadata file (.fsv_meta)
+use authfs_fsverity_metadata_bindgen::{
+ fsverity_metadata_header, FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7,
+ FSVERITY_SIGNATURE_TYPE_RAW,
+};
+
+use std::cmp::min;
+use std::os::unix::fs::MetadataExt;
+
+/// Structure for parsed metadata.
+pub struct FSVerityMetadata {
+ /// Header for the metadata.
+ pub header: fsverity_metadata_header,
+
+ /// Optional signature for the metadata.
+ pub signature: Option<Vec<u8>>,
+
+ metadata_file: File,
+
+ merkle_tree_offset: u64,
+}
+
+impl FSVerityMetadata {
+ /// Read the raw Merkle tree from the metadata, if it exists. The API semantics is similar to a
+ /// regular pread(2), and may not return full requested buffer.
+ pub fn read_merkle_tree(&self, offset: u64, buf: &mut [u8]) -> io::Result<usize> {
+ let start = self.merkle_tree_offset + offset;
+ let end = min(self.metadata_file.metadata()?.size(), start + buf.len() as u64);
+ let read_size = (end - start) as usize;
+ debug_assert!(read_size <= buf.len());
+ self.metadata_file.read_exact_at(&mut buf[..read_size], start)?;
+ Ok(read_size)
+ }
+}
+
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::{self, Read, Seek};
+use std::mem::{size_of, zeroed};
+use std::os::unix::fs::FileExt;
+use std::path::{Path, PathBuf};
+use std::slice::from_raw_parts_mut;
+
+/// Common block and page size in Linux.
+pub const CHUNK_SIZE: u64 = authfs_fsverity_metadata_bindgen::CHUNK_SIZE;
+
+/// Derive a path of metadata for a given path.
+/// e.g. "system/framework/foo.jar" -> "system/framework/foo.jar.fsv_meta"
+pub fn get_fsverity_metadata_path(path: &Path) -> PathBuf {
+ let mut os_string: OsString = path.into();
+ os_string.push(".fsv_meta");
+ os_string.into()
+}
+
+/// Parse metadata from given file, and returns a structure for the metadata.
+pub fn parse_fsverity_metadata(mut metadata_file: File) -> io::Result<Box<FSVerityMetadata>> {
+ let header_size = size_of::<fsverity_metadata_header>();
+
+ // SAFETY: the header doesn't include any pointers
+ let header: fsverity_metadata_header = unsafe {
+ let mut header: fsverity_metadata_header = zeroed();
+ let buffer = from_raw_parts_mut(
+ &mut header as *mut fsverity_metadata_header as *mut u8,
+ header_size,
+ );
+ metadata_file.read_exact(buffer)?;
+
+ // TODO(inseob): This doesn't seem ideal. Maybe we can consider nom?
+ header.version = u32::from_le(header.version);
+ header.descriptor.data_size = u64::from_le(header.descriptor.data_size);
+ header.signature_type = u32::from_le(header.signature_type);
+ header.signature_size = u32::from_le(header.signature_size);
+ header
+ };
+
+ if header.version != 1 {
+ return Err(io::Error::new(io::ErrorKind::Other, "unsupported metadata version"));
+ }
+
+ let signature = match header.signature_type {
+ FSVERITY_SIGNATURE_TYPE_NONE => None,
+ FSVERITY_SIGNATURE_TYPE_PKCS7 | FSVERITY_SIGNATURE_TYPE_RAW => {
+ // TODO: unpad pkcs7?
+ let mut buf = vec![0u8; header.signature_size as usize];
+ metadata_file.read_exact(&mut buf)?;
+ Some(buf)
+ }
+ _ => return Err(io::Error::new(io::ErrorKind::Other, "unknown signature type")),
+ };
+
+ // merkle tree is at the next 4K boundary
+ let merkle_tree_offset =
+ (metadata_file.stream_position()? + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
+
+ Ok(Box::new(FSVerityMetadata { header, signature, metadata_file, merkle_tree_offset }))
+}
diff --git a/authfs/src/fsverity/verifier.rs b/authfs/src/fsverity/verifier.rs
index 4a18c6a..1add37a 100644
--- a/authfs/src/fsverity/verifier.rs
+++ b/authfs/src/fsverity/verifier.rs
@@ -137,7 +137,7 @@
authenticator: &A,
chunked_file: F,
file_size: u64,
- sig: Vec<u8>,
+ sig: Option<&[u8]>,
merkle_tree: M,
) -> Result<VerifiedFileReader<F, M>, FsverityError> {
let mut buf = [0u8; CHUNK_SIZE as usize];
@@ -147,7 +147,7 @@
}
let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
let formatted_digest = build_fsverity_formatted_digest(&root_hash, file_size)?;
- let valid = authenticator.verify(&sig, &formatted_digest)?;
+ let valid = authenticator.verify(sig, &formatted_digest)?;
if valid {
Ok(VerifiedFileReader { chunked_file, file_size, merkle_tree, root_hash })
} else {
@@ -175,9 +175,9 @@
use crate::auth::FakeAuthenticator;
use crate::file::ReadByChunk;
use anyhow::Result;
+ use authfs_fsverity_metadata::{parse_fsverity_metadata, FSVerityMetadata};
use std::cmp::min;
- use std::fs::{self, File};
- use std::io::Read;
+ use std::fs::File;
use std::os::unix::fs::FileExt;
struct LocalFileReader {
@@ -210,7 +210,17 @@
}
}
- type LocalVerifiedFileReader = VerifiedFileReader<LocalFileReader, LocalFileReader>;
+ type LocalVerifiedFileReader = VerifiedFileReader<LocalFileReader, MerkleTreeReader>;
+
+ pub struct MerkleTreeReader {
+ metadata: Box<FSVerityMetadata>,
+ }
+
+ impl ReadByChunk for MerkleTreeReader {
+ fn read_chunk(&self, chunk_index: u64, buf: &mut ChunkBuffer) -> io::Result<usize> {
+ self.metadata.read_merkle_tree(chunk_index * CHUNK_SIZE, buf)
+ }
+ }
fn total_chunk_number(file_size: u64) -> u64 {
(file_size + 4095) / 4096
@@ -219,28 +229,28 @@
// Returns a reader with fs-verity verification and the file size.
fn new_reader_with_fsverity(
content_path: &str,
- merkle_tree_path: &str,
- signature_path: &str,
+ metadata_path: &str,
) -> Result<(LocalVerifiedFileReader, u64)> {
let file_reader = LocalFileReader::new(File::open(content_path)?)?;
let file_size = file_reader.len();
- let merkle_tree = LocalFileReader::new(File::open(merkle_tree_path)?)?;
- let mut sig = Vec::new();
- let _ = File::open(signature_path)?.read_to_end(&mut sig)?;
+ let metadata = parse_fsverity_metadata(File::open(metadata_path)?)?;
let authenticator = FakeAuthenticator::always_succeed();
Ok((
- VerifiedFileReader::new(&authenticator, file_reader, file_size, sig, merkle_tree)?,
+ VerifiedFileReader::new(
+ &authenticator,
+ file_reader,
+ file_size,
+ metadata.signature.clone().as_deref(),
+ MerkleTreeReader { metadata },
+ )?,
file_size,
))
}
#[test]
fn fsverity_verify_full_read_4k() -> Result<()> {
- let (file_reader, file_size) = new_reader_with_fsverity(
- "testdata/input.4k",
- "testdata/input.4k.merkle_dump",
- "testdata/input.4k.fsv_sig",
- )?;
+ let (file_reader, file_size) =
+ new_reader_with_fsverity("testdata/input.4k", "testdata/input.4k.fsv_meta")?;
for i in 0..total_chunk_number(file_size) {
let mut buf = [0u8; 4096];
@@ -251,11 +261,8 @@
#[test]
fn fsverity_verify_full_read_4k1() -> Result<()> {
- let (file_reader, file_size) = new_reader_with_fsverity(
- "testdata/input.4k1",
- "testdata/input.4k1.merkle_dump",
- "testdata/input.4k1.fsv_sig",
- )?;
+ let (file_reader, file_size) =
+ new_reader_with_fsverity("testdata/input.4k1", "testdata/input.4k1.fsv_meta")?;
for i in 0..total_chunk_number(file_size) {
let mut buf = [0u8; 4096];
@@ -266,11 +273,8 @@
#[test]
fn fsverity_verify_full_read_4m() -> Result<()> {
- let (file_reader, file_size) = new_reader_with_fsverity(
- "testdata/input.4m",
- "testdata/input.4m.merkle_dump",
- "testdata/input.4m.fsv_sig",
- )?;
+ let (file_reader, file_size) =
+ new_reader_with_fsverity("testdata/input.4m", "testdata/input.4m.fsv_meta")?;
for i in 0..total_chunk_number(file_size) {
let mut buf = [0u8; 4096];
@@ -283,8 +287,7 @@
fn fsverity_verify_bad_merkle_tree() -> Result<()> {
let (file_reader, _) = new_reader_with_fsverity(
"testdata/input.4m",
- "testdata/input.4m.merkle_dump.bad", // First leaf node is corrupted.
- "testdata/input.4m.fsv_sig",
+ "testdata/input.4m.fsv_meta.bad_merkle", // First leaf node is corrupted.
)?;
// A lowest broken node (a 4K chunk that contains 128 sha256 hashes) will fail the read
@@ -304,10 +307,15 @@
let authenticator = FakeAuthenticator::always_fail();
let file_reader = LocalFileReader::new(File::open("testdata/input.4m")?)?;
let file_size = file_reader.len();
- let merkle_tree = LocalFileReader::new(File::open("testdata/input.4m.merkle_dump")?)?;
- let sig = fs::read("testdata/input.4m.fsv_sig")?;
- assert!(VerifiedFileReader::new(&authenticator, file_reader, file_size, sig, merkle_tree)
- .is_err());
+ let metadata = parse_fsverity_metadata(File::open("testdata/input.4m.fsv_meta")?)?;
+ assert!(VerifiedFileReader::new(
+ &authenticator,
+ file_reader,
+ file_size,
+ metadata.signature.clone().as_deref(),
+ MerkleTreeReader { metadata },
+ )
+ .is_err());
Ok(())
}
}
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 421cc02..858b099 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -27,7 +27,7 @@
//! of the actual file name, the exposed file names through AuthFS are currently integer, e.g.
//! /mountpoint/42.
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, Result};
use log::error;
use std::convert::TryInto;
use std::path::{Path, PathBuf};
@@ -166,7 +166,7 @@
remote_fd: i32,
file_size: u64,
) -> Result<AuthFsEntry> {
- let signature = service.readFsveritySignature(remote_fd).context("Failed to read signature")?;
+ let signature = service.readFsveritySignature(remote_fd).ok();
let authenticator = FakeAuthenticator::always_succeed();
Ok(AuthFsEntry::VerifiedReadonly {
@@ -174,7 +174,7 @@
&authenticator,
RemoteFileReader::new(service.clone(), remote_fd),
file_size,
- signature,
+ signature.as_deref(),
RemoteMerkleTreeReader::new(service.clone(), remote_fd),
)?,
file_size,