Skip to content

Commit f7f24db

Browse files
committed
Auto merge of #155721 - cezarbbb:fix-archive-ice-148217, r=<try>
When archive format is wrong produce an error instead of ICE try-job: aarch64-apple try-job: x86_64-msvc-1 try-job: aarch64-msvc-1
2 parents 68ffae4 + 2080eff commit f7f24db

5 files changed

Lines changed: 157 additions & 4 deletions

File tree

compiler/rustc_codegen_ssa/src/back/archive.rs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ar_archive_writer::{
99
ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream,
1010
};
1111
pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
12-
use object::read::archive::ArchiveFile;
12+
use object::read::archive::{ArchiveFile, ArchiveKind as ObjectArchiveKind};
1313
use object::read::macho::FatArch;
1414
use rustc_data_structures::fx::FxIndexSet;
1515
use rustc_data_structures::memmap::Mmap;
@@ -320,6 +320,43 @@ pub trait ArchiveBuilder {
320320
fn build(self: Box<Self>, output: &Path) -> bool;
321321
}
322322

323+
fn target_archive_format_to_object_kind(format: &str) -> Option<ObjectArchiveKind> {
324+
match format {
325+
"gnu" => Some(ObjectArchiveKind::Gnu),
326+
"bsd" => Some(ObjectArchiveKind::Bsd),
327+
"darwin" => Some(ObjectArchiveKind::Bsd64),
328+
"coff" => Some(ObjectArchiveKind::Coff),
329+
"aix_big" => Some(ObjectArchiveKind::AixBig),
330+
_ => None,
331+
}
332+
}
333+
334+
fn archive_kinds_compatible(actual: ObjectArchiveKind, expected: ObjectArchiveKind) -> bool {
335+
matches!(
336+
(actual, expected),
337+
(ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu)
338+
| (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd)
339+
| (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd64)
340+
| (ObjectArchiveKind::Coff, ObjectArchiveKind::Coff)
341+
| (ObjectArchiveKind::AixBig, ObjectArchiveKind::AixBig)
342+
// 64-bit symbol table variants are compatible with their 32-bit counterparts
343+
| (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu)
344+
| (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd)
345+
| (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64)
346+
)
347+
}
348+
349+
fn archive_format_display_name(format: &str) -> String {
350+
match format {
351+
"gnu" | "Gnu" | "Gnu64" => "GNU".to_string(),
352+
"bsd" | "Bsd" => "BSD".to_string(),
353+
"darwin" | "Bsd64" => "Darwin".to_string(),
354+
"coff" | "Coff" => "COFF".to_string(),
355+
"aix_big" | "AixBig" => "AIX big".to_string(),
356+
other => other.to_string(),
357+
}
358+
}
359+
323360
pub struct ArArchiveBuilderBuilder;
324361

325362
impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder {
@@ -420,6 +457,21 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
420457
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
421458
let archive_index = self.src_archives.len();
422459

460+
if let Some(expected_kind) =
461+
target_archive_format_to_object_kind(&self.sess.target.archive_format)
462+
{
463+
let actual_kind = archive.kind();
464+
if actual_kind != ObjectArchiveKind::Unknown
465+
&& !archive_kinds_compatible(actual_kind, expected_kind)
466+
{
467+
self.sess.dcx().emit_warn(crate::errors::IncompatibleArchiveFormat {
468+
path: archive_path.clone(),
469+
actual: archive_format_display_name(&format!("{actual_kind:?}")),
470+
expected: archive_format_display_name(&self.sess.target.archive_format),
471+
});
472+
}
473+
}
474+
423475
for entry in archive.members() {
424476
let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
425477
let file_name = String::from_utf8(entry.name().to_vec())
@@ -482,9 +534,25 @@ impl<'a> ArArchiveBuilder<'a> {
482534
match entry {
483535
ArchiveEntry::FromArchive { archive_index, file_range } => {
484536
let src_archive = &self.src_archives[archive_index];
485-
486-
let data = &src_archive.1
487-
[file_range.0 as usize..file_range.0 as usize + file_range.1 as usize];
537+
let archive_data = &src_archive.1;
538+
let start = file_range.0 as usize;
539+
let end = start + file_range.1 as usize;
540+
if end > archive_data.len() {
541+
return Err(io_error_context(
542+
"invalid archive member",
543+
io::Error::new(
544+
io::ErrorKind::InvalidData,
545+
format!(
546+
"archive member at offset {start} with size {} \
547+
exceeds archive size {} in `{}`",
548+
file_range.1,
549+
archive_data.len(),
550+
src_archive.0.display(),
551+
),
552+
),
553+
));
554+
}
555+
let data = &archive_data[start..end];
488556

489557
Box::new(data) as Box<dyn AsRef<[u8]>>
490558
}

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,18 @@ pub(crate) struct UnknownArchiveKind<'a> {
671671
pub kind: &'a str,
672672
}
673673

674+
#[derive(Diagnostic)]
675+
#[diag("archive `{$path}` was built as {$actual} format, but the target expects {$expected}")]
676+
#[help(
677+
"this often occurs when using BSD-format archive tools on a GNU/Linux target; \
678+
rebuild the archive with the correct format for the target platform"
679+
)]
680+
pub(crate) struct IncompatibleArchiveFormat {
681+
pub path: PathBuf,
682+
pub actual: String,
683+
pub expected: String,
684+
}
685+
674686
#[derive(Diagnostic)]
675687
#[diag("linking static libraries is not supported for BPF")]
676688
pub(crate) struct BpfStaticlibNotSupported;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
extern "C" {
2+
fn foo() -> i32;
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int foo() { return 42; }
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Regression test for https://github.com/rust-lang/rust/issues/148217
2+
// Two-layer defense: a warning for incompatible archive format, and an error
3+
// (not ICE) for corrupt member offsets.
4+
5+
//@ ignore-cross-compile
6+
7+
use run_make_support::{cc, llvm_ar, path, rfs, rustc, static_lib_name};
8+
9+
fn main() {
10+
rfs::create_dir("archive");
11+
12+
cc().arg("-c").input("native.c").output("archive/native.o").run();
13+
14+
// Test 1 (first defense): BSD format archive on a GNU/Linux target should
15+
// emit a format mismatch warning.
16+
let bsd_archive = path("archive").join(static_lib_name("native_bsd"));
17+
llvm_ar().arg("rcus").arg("--format=bsd").output_input(&bsd_archive, "archive/native.o").run();
18+
rustc()
19+
.input("lib.rs")
20+
.crate_type("rlib")
21+
.library_search_path("archive")
22+
.arg("-lstatic=native_bsd")
23+
.run()
24+
.assert_stderr_contains("BSD")
25+
.assert_stderr_contains("GNU");
26+
27+
// Test 2 (second defense): corrupt archive with member offset exceeding
28+
// file boundary should produce an error, not an ICE.
29+
let corrupt_archive = path("archive").join(static_lib_name("corrupt"));
30+
create_corrupt_archive(&corrupt_archive);
31+
rustc()
32+
.input("lib.rs")
33+
.crate_type("rlib")
34+
.library_search_path("archive")
35+
.arg("-lstatic=corrupt")
36+
.run_fail()
37+
.assert_stderr_not_contains("panicked")
38+
.assert_stderr_not_contains("unexpectedly panicked")
39+
.assert_stderr_contains("archive");
40+
}
41+
42+
fn create_corrupt_archive(output_path: &std::path::Path) {
43+
use std::fs;
44+
use std::io::Write;
45+
46+
let mut archive = b"!<arch>\n".to_vec();
47+
48+
let member_name = "corrupt.o/ ";
49+
let mtime = "0 ";
50+
let uid = "0 ";
51+
let gid = "0 ";
52+
let mode = "100644 ";
53+
let size = "10000 ";
54+
let fmag = "`\n";
55+
56+
archive.extend_from_slice(member_name.as_bytes());
57+
archive.extend_from_slice(mtime.as_bytes());
58+
archive.extend_from_slice(uid.as_bytes());
59+
archive.extend_from_slice(gid.as_bytes());
60+
archive.extend_from_slice(mode.as_bytes());
61+
archive.extend_from_slice(size.as_bytes());
62+
archive.extend_from_slice(fmag.as_bytes());
63+
64+
archive.extend_from_slice(b"small_data");
65+
66+
archive.push(b'\n');
67+
68+
fs::write(output_path, archive).unwrap();
69+
}

0 commit comments

Comments
 (0)