Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read/archive: locate members by table in AIX archive #467

Merged
merged 4 commits into from
Sep 30, 2022
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 64 additions & 16 deletions src/read/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,11 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {

if file.kind == ArchiveKind::AixBig {
// The fixed length header is located just after magic number.
let file_header = data.read::<archive::AIXFileHeader>(&mut tail)
let file_header = data
.read::<archive::AIXFileHeader>(&mut tail)
.read_error("Invalid AIX big archive file header")?;
file.offset = parse_u64_digits(&file_header.fstmoff, 10)
.read_error("Invalid offset for first archive member in AIX big archive")?;

// Member table is located just after all archive members.
file.len = parse_u64_digits(&file_header.memoff, 10)
.read_error("Invalid offset for member table of AIX big archive")?;
// Read the span of symbol table.
let symtbl64 = parse_u64_digits(&file_header.gst64off, 10)
.read_error("Invalid offset to 64-bit symbol table in AIX big archive")?;
if symtbl64 > 0 {
Expand All @@ -93,6 +90,56 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
file.symbols = (symtbl, len);
}
}

// Big archive member table lists file entries with offsets and names. To avoid
// potential infinite loop (members are double-linked list), the iterator goes
// through the table instead of real members.
let mut member_table_offset = parse_u64_digits(&file_header.memoff, 10)
.read_error("Invalid offset for member table of AIX big archive")?;
if member_table_offset > 0 {
// The table itself is also a file with header.
let header = data
.read::<archive::AixHeader>(&mut member_table_offset)
.read_error("Invalid AIX big archive member header")?;
let terminator = data
.read_bytes(&mut member_table_offset, 2)
.read_error("Invalid AIX big archive terminator")?;
if terminator != archive::TERMINATOR {
return Err(Error("Invalid AIX big archive terminator"));
}

// Structure of member table:
// Number of entries (20 bytes)
// Offsets of each entry (20*N bytes)
// Names string table (the rest of bytes to fill size defined in header)
let table_size = parse_u64_digits(&header.size, 10)
.read_error("Invalid AIX big archive member size")?;
let members_count = data
.read_bytes_at(member_table_offset, 30)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have been a length of 20?

I'll do a followup PR for some other things, so I'll change this if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it should be 20, Thanks!

.ok()
.and_then(|bytes| parse_u64_digits(bytes, 10))
.read_error("Invalid member count in AIX big archive")?;
file.offset = member_table_offset
.checked_add(20)
.read_error("AIX big archive offset overflow")?;
let member_offsets_size = members_count
.checked_mul(20)
.read_error("AIX big archive length overflow")?;
file.len = member_offsets_size
.checked_add(file.offset)
.read_error("AIX big archive length overflow")?;
let names_size = table_size
.checked_sub(member_offsets_size)
.and_then(|size| size.checked_sub(20))
.read_error("AIX big archive string table size overflow")?;
file.names = data
.read_bytes_at(file.len, names_size)
.read_error("Invalid AIX big archive member name list")?;
} else {
// The offset would be zero if archive contains no file.
file.offset = 0;
file.len = 0;
}
return Ok(file);
} else if tail < len {
// The first few members may be special, so parse them.
Expand Down Expand Up @@ -303,34 +350,34 @@ impl<'data> ArchiveMember<'data> {
) -> read::Result<Self> {
// The format was described at
// https://www.ibm.com/docs/en/aix/7.3?topic=formats-ar-file-format-big
let file_offset_dec = data
.read_bytes(offset, 20)
.read_error("Invalid AIX big archive file member offset")?;
let mut file_offset = parse_u64_digits(file_offset_dec, 10)
.read_error("Invalid AIX big archive file member offset")?;
let header = data
.read::<archive::AixHeader>(offset)
.read::<archive::AixHeader>(&mut file_offset)
.read_error("Invalid AIX big archive member header")?;
let name_length = parse_u64_digits(&header.namlen, 10)
.read_error("Invalid AIX big archive member name length")?;
let name = data
.read_bytes(offset, name_length)
.read_bytes(&mut file_offset, name_length)
.read_error("Invalid AIX big archive member name")?;

// The actual data for a file member begins at the first even-byte boundary beyond the
// member header and continues for the number of bytes specified by the ar_size field. The
// ar command inserts null bytes for padding where necessary.
if *offset & 1 != 0 {
*offset = offset.saturating_add(1);
if file_offset & 1 != 0 {
file_offset = file_offset.saturating_add(1);
}
// Because of the even-byte boundary, we have to read and check terminator after header.
let terminator = data
.read_bytes(offset, 2)
.read_bytes(&mut file_offset, 2)
.read_error("Invalid AIX big archive head terminator")?;
if terminator != archive::TERMINATOR {
return Err(Error("Invalid AIX big archive terminator"));
}
let file_offset = *offset;
let nextmbroff = parse_u64_digits(&header.nxtmem, 10)
.read_error("Invalid next member offset in AIX big archive")?;

// Move the offset to next member offset
*offset = nextmbroff;
let file_size = parse_u64_digits(&header.size, 10)
.read_error("Invalid archive member size in AIX big archive")?;
Ok(ArchiveMember {
Expand Down Expand Up @@ -654,6 +701,7 @@ mod tests {
let data = &data[..];
let archive = ArchiveFile::parse(data).unwrap();
assert_eq!(archive.kind(), ArchiveKind::AixBig);
assert_eq!(archive.names, b"0123456789abcdef\0fedcba9876543210\0");
let mut members = archive.members();

let member = members.next().unwrap().unwrap();
Expand Down