Skip to content

Commit

Permalink
feat: support handle very large uid/gid in PAX style tar (#333)
Browse files Browse the repository at this point in the history
* refactor pax_extensions_size() to pax_extensions_value()

Signed-off-by: Qinqi Qu <quqinqi@linux.alibaba.com>

* make the handling of pax extensions more generic

Signed-off-by: Qinqi Qu <quqinqi@linux.alibaba.com>

* feat: support handle very large uid/gid in PAX style tar

This commit add process to read large uid/gid from PAX extensions
to fix very large UIDs/GIDs (>=2097151, limit of USTAR tar) lost
in PAX style tar during unpack.

Fix: #332

Signed-off-by: Qinqi Qu <quqinqi@linux.alibaba.com>

---------

Signed-off-by: Qinqi Qu <quqinqi@linux.alibaba.com>
  • Loading branch information
adamqqqplay committed Aug 7, 2023
1 parent 826907c commit 1d37489
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 12 deletions.
23 changes: 16 additions & 7 deletions src/archive.rs
Expand Up @@ -10,7 +10,7 @@ use std::path::Path;
use crate::entry::{EntryFields, EntryIo};
use crate::error::TarError;
use crate::other;
use crate::pax::pax_extensions_size;
use crate::pax::*;
use crate::{Entry, GnuExtSparseHeader, GnuSparseHeader, Header};

/// A top-level representation of an archive file.
Expand Down Expand Up @@ -275,7 +275,7 @@ impl<'a, R: Read> Iterator for Entries<'a, R> {
impl<'a> EntriesFields<'a> {
fn next_entry_raw(
&mut self,
pax_size: Option<u64>,
pax_extensions: Option<&[u8]>,
) -> io::Result<Option<Entry<'a, io::Empty>>> {
let mut header = Header::new_old();
let mut header_pos = self.next;
Expand Down Expand Up @@ -315,6 +315,19 @@ impl<'a> EntriesFields<'a> {
return Err(other("archive header checksum mismatch"));
}

let mut pax_size: Option<u64> = None;
if let Some(pax_extensions_ref) = &pax_extensions {
pax_size = pax_extensions_value(pax_extensions_ref, PAX_SIZE);

if let Some(pax_uid) = pax_extensions_value(pax_extensions_ref, PAX_UID) {
header.set_uid(pax_uid);
}

if let Some(pax_gid) = pax_extensions_value(pax_extensions_ref, PAX_GID) {
header.set_gid(pax_gid);
}
}

let file_pos = self.next;
let mut size = header.entry_size()?;
if size == 0 {
Expand Down Expand Up @@ -360,11 +373,10 @@ impl<'a> EntriesFields<'a> {
let mut gnu_longname = None;
let mut gnu_longlink = None;
let mut pax_extensions = None;
let mut pax_size = None;
let mut processed = 0;
loop {
processed += 1;
let entry = match self.next_entry_raw(pax_size)? {
let entry = match self.next_entry_raw(pax_extensions.as_deref())? {
Some(entry) => entry,
None if processed > 1 => {
return Err(other(
Expand Down Expand Up @@ -408,9 +420,6 @@ impl<'a> EntriesFields<'a> {
));
}
pax_extensions = Some(EntryFields::from(entry).read_all()?);
if let Some(pax_extensions_ref) = &pax_extensions {
pax_size = pax_extensions_size(pax_extensions_ref);
}
continue;
}

Expand Down
40 changes: 35 additions & 5 deletions src/pax.rs
@@ -1,9 +1,39 @@
#![allow(dead_code)]
use std::io;
use std::slice;
use std::str;

use crate::other;

// Keywords for PAX extended header records.
pub const PAX_NONE: &str = ""; // Indicates that no PAX key is suitable
pub const PAX_PATH: &str = "path";
pub const PAX_LINKPATH: &str = "linkpath";
pub const PAX_SIZE: &str = "size";
pub const PAX_UID: &str = "uid";
pub const PAX_GID: &str = "gid";
pub const PAX_UNAME: &str = "uname";
pub const PAX_GNAME: &str = "gname";
pub const PAX_MTIME: &str = "mtime";
pub const PAX_ATIME: &str = "atime";
pub const PAX_CTIME: &str = "ctime"; // Removed from later revision of PAX spec, but was valid
pub const PAX_CHARSET: &str = "charset"; // Currently unused
pub const PAX_COMMENT: &str = "comment"; // Currently unused

pub const PAX_SCHILYXATTR: &str = "SCHILY.xattr.";

// Keywords for GNU sparse files in a PAX extended header.
pub const PAX_GNUSPARSE: &str = "GNU.sparse.";
pub const PAX_GNUSPARSENUMBLOCKS: &str = "GNU.sparse.numblocks";
pub const PAX_GNUSPARSEOFFSET: &str = "GNU.sparse.offset";
pub const PAX_GNUSPARSENUMBYTES: &str = "GNU.sparse.numbytes";
pub const PAX_GNUSPARSEMAP: &str = "GNU.sparse.map";
pub const PAX_GNUSPARSENAME: &str = "GNU.sparse.name";
pub const PAX_GNUSPARSEMAJOR: &str = "GNU.sparse.major";
pub const PAX_GNUSPARSEMINOR: &str = "GNU.sparse.minor";
pub const PAX_GNUSPARSESIZE: &str = "GNU.sparse.size";
pub const PAX_GNUSPARSEREALSIZE: &str = "GNU.sparse.realsize";

/// An iterator over the pax extensions in an archive entry.
///
/// This iterator yields structures which can themselves be parsed into
Expand All @@ -30,25 +60,25 @@ pub struct PaxExtension<'entry> {
value: &'entry [u8],
}

pub fn pax_extensions_size(a: &[u8]) -> Option<u64> {
pub fn pax_extensions_value(a: &[u8], key: &str) -> Option<u64> {
for extension in PaxExtensions::new(a) {
let current_extension = match extension {
Ok(ext) => ext,
Err(_) => return None,
};
if current_extension.key() != Ok("size") {
if current_extension.key() != Ok(key) {
continue;
}

let value = match current_extension.value() {
Ok(value) => value,
Err(_) => return None,
};
let size = match value.parse::<u64>() {
Ok(size) => size,
let result = match value.parse::<u64>() {
Ok(result) => result,
Err(_) => return None,
};
return Some(size);
return Some(result);
}
None
}
Expand Down
27 changes: 27 additions & 0 deletions tests/all.rs
Expand Up @@ -1488,3 +1488,30 @@ fn ownership_preserving() {
assert!(ar.unpack(td.path()).is_err());
}
}

#[test]
#[cfg(unix)]
fn pax_and_gnu_uid_gid() {
let tarlist = [tar!("biguid_gnu.tar"), tar!("biguid_pax.tar")];

for file in &tarlist {
let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());
let rdr = Cursor::new(file);
let mut ar = Archive::new(rdr);
ar.set_preserve_ownerships(true);

if unsafe { libc::getuid() } == 0 {
t!(ar.unpack(td.path()));
let meta = fs::metadata(td.path().join("test.txt")).unwrap();
let uid = std::os::unix::prelude::MetadataExt::uid(&meta);
let gid = std::os::unix::prelude::MetadataExt::gid(&meta);
// 4294967294 = u32::MAX - 1
assert_eq!(uid, 4294967294);
assert_eq!(gid, 4294967294);
} else {
// it's not possible to unpack tar while preserving ownership
// without root permissions
assert!(ar.unpack(td.path()).is_err());
}
}
}
Binary file added tests/archives/biguid_gnu.tar
Binary file not shown.
Binary file added tests/archives/biguid_pax.tar
Binary file not shown.

0 comments on commit 1d37489

Please sign in to comment.