Mercurial > public > mercurial-scm > hg-stable
diff rust/hg-core/src/revlog/revlog.rs @ 45537:b0d6309ff50c
hg-core: check data integrity in `Revlog`
Check that the hash of the data reconstructed from deltas
matches the hash stored in the revision.
Differential Revision: https://phab.mercurial-scm.org/D9005
author | Antoine Cezar <antoine.cezar@octobus.net> |
---|---|
date | Wed, 02 Sep 2020 15:23:25 +0200 |
parents | 26c53ee51c68 |
children | 4f11a67a12fb |
line wrap: on
line diff
--- a/rust/hg-core/src/revlog/revlog.rs Wed Sep 23 12:26:16 2020 +0200 +++ b/rust/hg-core/src/revlog/revlog.rs Wed Sep 02 15:23:25 2020 +0200 @@ -5,12 +5,15 @@ use std::path::Path; use byteorder::{BigEndian, ByteOrder}; +use crypto::digest::Digest; +use crypto::sha1::Sha1; use flate2::read::ZlibDecoder; use memmap::{Mmap, MmapOptions}; use micro_timer::timed; use zstd; use super::index::Index; +use super::node::{NODE_BYTES_LENGTH, NULL_NODE_ID}; use super::patch; use crate::revlog::Revision; @@ -93,13 +96,52 @@ .map_err(|_| RevlogError::Corrupted)?; } - if delta_chain.is_empty() { - Ok(entry.data()?.into()) + // TODO do not look twice in the index + let index = self.index(); + let index_entry = + index.get_entry(rev).ok_or(RevlogError::InvalidRevision)?; + + let data: Vec<u8> = if delta_chain.is_empty() { + entry.data()?.into() + } else { + Revlog::build_data_from_deltas(entry, &delta_chain)? + }; + + if self.check_hash( + index_entry.p1(), + index_entry.p2(), + index_entry.hash(), + &data, + ) { + Ok(data) } else { - Revlog::build_data_from_deltas(entry, &delta_chain) + Err(RevlogError::Corrupted) } } + /// Check the hash of some given data against the recorded hash. + pub fn check_hash( + &self, + p1: Revision, + p2: Revision, + expected: &[u8], + data: &[u8], + ) -> bool { + let index = self.index(); + let e1 = index.get_entry(p1); + let h1 = match e1 { + Some(ref entry) => entry.hash(), + None => &NULL_NODE_ID, + }; + let e2 = index.get_entry(p2); + let h2 = match e2 { + Some(ref entry) => entry.hash(), + None => &NULL_NODE_ID, + }; + + hash(data, &h1, &h2).as_slice() == expected + } + /// Build the full data of a revision out its snapshot /// and its deltas. #[timed] @@ -234,6 +276,23 @@ BigEndian::read_u16(&index_bytes[2..=3]) } +/// Calculate the hash of a revision given its data and its parents. +fn hash(data: &[u8], p1_hash: &[u8], p2_hash: &[u8]) -> Vec<u8> { + let mut hasher = Sha1::new(); + let (a, b) = (p1_hash, p2_hash); + if a > b { + hasher.input(b); + hasher.input(a); + } else { + hasher.input(a); + hasher.input(b); + } + hasher.input(data); + let mut hash = vec![0; NODE_BYTES_LENGTH]; + hasher.result(&mut hash); + hash +} + #[cfg(test)] mod tests { use super::*;