///|
/// Check indexed metadata coverage and return the first compressed block.
fn Image::check_index(
self : Image,
table : Int,
count : Int,
width : Int,
end : Int,
) -> Int raise SquashError {
guard count > 0 && count <= 1000000 else {
raise Limit("indexed table count")
}
let bytes = count * width
let blocks = (bytes + 8191) / 8192
guard table >= self.superblock.directory_table &&
table <= end &&
blocks * 8 == end - table else {
raise Invalid(table, "index table extent")
}
let start = self.pointer(table)
guard start >= self.superblock.directory_table && start < table else {
raise Invalid(table, "index first block")
}
let mut next = start
for i in 0.. Map[Int, Bool] raise SquashError {
let boundaries : Map[Int, Bool] = Map([])
let mut at = start
while at < end {
boundaries[at] = true
let (_, next) = self.metadata_block(at)
guard next > at && next <= end else {
raise Invalid(at, "metadata crosses table boundary")
}
at = next
}
guard at == end else { raise Invalid(at, "metadata table boundary") }
boundaries
}
///|
fn Image::validate_layout(
self : Image,
tree : Array[TreeEntry],
) -> Unit raise SquashError {
let h = self.superblock
guard h.inodes <= 100000 && h.fragments <= 1000000 else {
raise Limit("verification table count")
}
guard (h.flags & 0xf000) == 0 else {
raise Invalid(24, "reserved superblock flags")
}
guard ((h.flags & 0x80) != 0) == (h.export_table != 0xffffffffffffffffUL) else {
raise Invalid(88, "export flag/table mismatch")
}
guard (h.flags & 0x10) == 0 || h.fragments == 0 else {
raise Invalid(16, "no-fragments flag mismatch")
}
let mut end = h.bytes_used
if h.xattr_table != 0xffffffffffffffffUL {
guard (h.flags & 0x200) == 0 else {
raise Invalid(56, "no-xattrs flag mismatch")
}
let (start, count, index_table) = self.xattr_header()
// The xattr header separates metadata from its index.
let blocks = (count * 16 + 8191) / 8192
guard index_table + blocks * 8 == end else {
raise Invalid(index_table, "xattr index extent")
}
let mut next = self.pointer(index_table)
let ids_start = next
for i in 0.. 0 {
end = self.check_index(h.fragment_table, h.fragments, 16, end)
}
let directory_end = end
let dir_blocks = self.scan_metadata(h.directory_table, directory_end)
let inode_blocks = self.scan_metadata(h.inode_table, h.directory_table)
for e in tree {
let relative = index(e.inode.reference >> 16, 32)
guard inode_blocks.contains(h.inode_table + relative) else {
raise Invalid(relative, "inode points inside a metadata block")
}
if e.inode.content is Directory(block, _, size) {
guard size == 0 || dir_blocks.contains(block) else {
raise Invalid(block, "directory points inside a metadata block")
}
let cursor : Cursor = {
image: self,
block,
offset: match e.inode.content {
Directory(_, off, _) => off
_ => 0
},
}
ignore(cursor.take(size))
guard cursor.block <= directory_end &&
(cursor.block < directory_end || cursor.offset == 0) else {
raise Invalid(block, "directory extent crosses table")
}
}
}
// Walk every packed inode record, catching unreachable records and references
// into the middle of records, including extended directory index payloads.
let cursor : Cursor = { image: self, block: h.inode_table, offset: 0, }
let references : Map[UInt64, Bool] = Map([])
for _ in 0.. ignore(cursor.take(16))
8 => {
let b = cursor.take(24)
let count = u16(b, 16)
let dir_size = index(u32(b, 4).to_uint64(), cursor.block)
let mut previous_index = -1
let mut previous_name : Bytes? = None
for _ in 0.. previous_index &&
logical < dir_size - 3 &&
relative < directory_end - h.directory_table &&
dir_blocks.contains(h.directory_table + relative) else {
raise Invalid(
cursor.block,
"invalid directory index offset or block",
)
}
let name_size = index(u32(idx, 8).to_uint64(), cursor.block)
guard name_size < 256 else {
raise Invalid(cursor.block, "directory index name length")
}
let name = cursor.take(name_size + 1)
guard !name.iter().any(c => c == 0 || c == b'/') else {
raise Invalid(cursor.block, "directory index name")
}
if previous_name is Some(before) {
guard before.lexical_compare(name) < 0 else {
raise Invalid(cursor.block, "directory index ordering")
}
}
previous_index = logical
previous_name = Some(name)
}
}
2 | 9 => {
let b = cursor.take(if kind == 2 { 16 } else { 40 })
let size = if kind == 2 {
index(u32(b, 12).to_uint64(), cursor.block)
} else {
index(u64(b, 8), cursor.block)
}
let fragment = u32(b, if kind == 2 { 4 } else { 28 })
if kind == 9 {
guard u64(b, 16) <= size.to_uint64() else {
raise Invalid(cursor.block, "sparse byte count exceeds file size")
}
}
let blocks = size / h.block_size +
(if fragment == 0xffffffffU && size % h.block_size != 0 {
1
} else {
0
})
ignore(cursor.take(blocks * 4))
}
3 | 10 => {
let b = cursor.take(8)
let size = index(u32(b, 4).to_uint64(), cursor.block)
guard size > 0 && size <= 65536 else { raise Limit("symlink size") }
ignore(cursor.take(size + (if kind == 10 { 4 } else { 0 })))
}
4 | 5 => ignore(cursor.take(8))
6 | 7 => ignore(cursor.take(4))
11 | 12 => ignore(cursor.take(12))
13 | 14 => ignore(cursor.take(8))
_ => raise Invalid(cursor.block, "unknown inode record")
}
guard cursor.block <= h.directory_table else {
raise Invalid(cursor.block, "inode record crosses table")
}
}
guard cursor.block == h.directory_table && cursor.offset == 0 else {
raise Invalid(
cursor.block,
"inode table contains excess records or trailing bytes",
)
}
for e in tree {
guard references.contains(e.inode.reference) else {
raise Invalid(e.inode.number, "reference is not inode start")
}
}
}