Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions include/eld/Diagnostics/DiagReaders.inc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ DIAG(error_unsupported_section_in_executable, DiagnosticEngine::Error,
"Unsupported section %0 in executable input file %1")
DIAG(error_executable_requires_just_symbols, DiagnosticEngine::Error,
"executable input file `%0' can only be used with --just-symbols")
DIAG(error_tar_archive_input, DiagnosticEngine::Error,
"%0: tar archives cannot be used as input files")
DIAG(verbose_symbol_has_aliases, DiagnosticEngine::Verbose,
"Global symbol %0 from input file %1 has aliases: %2")
DIAG(error_create_archive_member_input, DiagnosticEngine::Error,
Expand Down
4 changes: 4 additions & 0 deletions include/eld/Support/InputTarReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class InputTarReader {

using FileMap = llvm::StringMap<std::string>;

/// Return true if \p Data starts with a tar header block, recognized by its
/// checksum.
static bool isTarArchive(llvm::StringRef Data);

/// Parse tar bytes directly from memory.
static eld::Expected<FileMap> untar(llvm::StringRef TarData);

Expand Down
8 changes: 8 additions & 0 deletions lib/Object/ObjectLinker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "eld/Script/ScriptSymbol.h"
#include "eld/Script/StrToken.h"
#include "eld/Script/VersionScript.h"
#include "eld/Support/InputTarReader.h"
#include "eld/Support/Memory.h"
#include "eld/Support/MsgHandling.h"
#include "eld/Support/RegisterTimer.h"
Expand Down Expand Up @@ -3946,6 +3947,13 @@ bool ObjectLinker::readAndProcessInput(Input *Input, bool IsPostLto) {
}
// try to parse input as a linker script
else if (CurInput->getKind() == InputFile::GNULinkerScriptKind) {
// Inputs in an unrecognized format end up here. Diagnose tar archives
// instead of lexing their binary contents as a linker script.
if (InputTarReader::isTarArchive(CurInput->getContents())) {
ThisConfig.raise(Diag::error_tar_archive_input) << Input->decoratedPath();
ThisModule->setFailure(true);
return false;
}
eld::RegisterTimer T("Read Linker Script", "Read all Input files",
ThisConfig.options().printTimingStats());
if (layoutInfo)
Expand Down
23 changes: 22 additions & 1 deletion lib/Support/InputTarReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ constexpr size_t TarBlockSize = 512;
// We consume only what we need for linking workflows: `name`, `size`,
// `typeflag`, and `prefix`; parse numeric values as octal; advance each entry
// by payload size rounded up to 512-byte alignment; and keep only regular
// files in the in-memory map.
// files in the in-memory map. `chksum` is only used to recognize tar data.
constexpr size_t NameOffset = 0;
constexpr size_t NameSize = 100;
constexpr size_t SizeOffset = 124;
constexpr size_t SizeSize = 12;
constexpr size_t ChecksumOffset = 148;
constexpr size_t ChecksumSize = 8;
constexpr size_t TypeFlagOffset = 156;
constexpr size_t PrefixOffset = 345;
constexpr size_t PrefixSize = 155;
Expand Down Expand Up @@ -133,6 +135,25 @@ static eld::Expected<void> forEachEntry(llvm::StringRef TarData,

} // namespace

bool InputTarReader::isTarArchive(llvm::StringRef Data) {
if (Data.size() < TarBlockSize)
return false;

// Every header format, including V7 which has no magic, stores in `chksum`
// the octal sum of the header bytes, with `chksum` itself counted as spaces.
llvm::StringRef Field =
Data.substr(ChecksumOffset, ChecksumSize).trim(llvm::StringRef(" \0", 2));
uint64_t Checksum = 0;
if (Field.getAsInteger(/*Radix=*/8, Checksum))
return false;

uint64_t Sum = ChecksumSize * ' ';
for (size_t I = 0; I < TarBlockSize; ++I)
if (I < ChecksumOffset || I >= ChecksumOffset + ChecksumSize)
Sum += static_cast<unsigned char>(Data[I]);
return Sum == Checksum;
}
Comment thread
ricardoasouz marked this conversation as resolved.

eld::Expected<InputTarReader::FileMap>
InputTarReader::untar(llvm::StringRef TarData) {
FileMap Files;
Expand Down
10 changes: 10 additions & 0 deletions test/Common/standalone/Sniffing/Inputs/make_tar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python3
# Usage: make_tar.py FILE OUTPUT
# Writes OUTPUT as a tar archive holding FILE, like `tar cf OUTPUT FILE`.
import os
import sys
import tarfile

src, out = sys.argv[1:]
with tarfile.open(out, "w", format=tarfile.GNU_FORMAT) as tar:
tar.add(src, arcname=os.path.basename(src))
25 changes: 25 additions & 0 deletions test/Common/standalone/Sniffing/TarArchiveInput.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#---TarArchiveInput.test-------------------------- Executable -----------------#
#BEGIN_COMMENT
# Inputs that are not ELF, bitcode or ar archive files are parsed as linker
# scripts. This tests that the linker recognizes a tar archive given as an
# input file, on the command line or through INPUT() or GROUP(), and errors
# out instead of lexing it. With -b binary, the archive is still raw data.
#END_COMMENT
#START_TEST
RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.1.o
RUN: %python %p/Inputs/make_tar.py %t1.1.o %t1.a.tar
RUN: %not %link %linkopts %t1.a.tar -o %t2.out 2>&1 | %filecheck %s
RUN: %not %link %linkopts %t1.1.o %t1.a.tar -o %t2.out 2>&1 | %filecheck %s
RUN: echo "INPUT(%t1.a.tar)" > %t1.input.t
RUN: %not %link %linkopts %t1.1.o %t1.input.t -o %t2.out 2>&1 | %filecheck %s
RUN: echo "GROUP(%t1.a.tar)" > %t1.group.t
RUN: %not %link %linkopts %t1.1.o %t1.group.t -o %t2.out 2>&1 | %filecheck %s
RUN: %link %linkopts %t1.1.o -b binary %t1.a.tar -o %t2.out
RUN: %readelf -s %t2.out | %filecheck %s --check-prefix=BINARY
#END_TEST

CHECK-NOT: Ignoring invalid ASCII character
CHECK: Error: {{.*}}a.tar: tar archives cannot be used as input files
CHECK-NOT: has errors

BINARY: _binary_{{.*}}_a_tar_start
41 changes: 41 additions & 0 deletions test/UnitTests/TarReaderTests/TarReaderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"

#include <algorithm>
#include <cstdio>

using namespace eld;

namespace {
Expand Down Expand Up @@ -111,3 +114,41 @@ TEST(TarReaderTests, FindFileReturnsMemoryBufferRef) {
auto MissingOrErr = InputTarReader::findFile(TarData, "missing.txt");
EXPECT_FALSE(static_cast<bool>(MissingOrErr));
}

TEST(TarReaderTests, RecognizesTarArchives) {
std::string TarData = readFile(createTar({{"a.txt", "hello"}}));
EXPECT_TRUE(InputTarReader::isTarArchive(TarData));

// A name that does not fit in a ustar header makes TarWriter start the
// archive with a pax extended header.
std::string LongName(200, 'n');
EXPECT_TRUE(
InputTarReader::isTarArchive(readFile(createTar({{LongName, "hello"}}))));

// V7 headers have no magic: clear the fields that POSIX added to the first
// header and recompute its checksum.
std::string V7 = TarData;
std::fill(V7.begin() + 257, V7.begin() + 512, '\0');
std::fill(V7.begin() + 148, V7.begin() + 156, ' ');
unsigned Sum = 0;
for (size_t I = 0; I < 512; ++I)
Sum += static_cast<unsigned char>(V7[I]);
std::snprintf(&V7[148], 8, "%06o", Sum);
EXPECT_TRUE(InputTarReader::isTarArchive(V7));
}

TEST(TarReaderTests, RejectsNonTarData) {
std::string TarData = readFile(createTar({{"a.txt", "hello"}}));
// Any change to the header invalidates its checksum.
std::string Corrupted = TarData;
Corrupted[0] ^= 1;
EXPECT_FALSE(InputTarReader::isTarArchive(Corrupted));
EXPECT_FALSE(
InputTarReader::isTarArchive(llvm::StringRef(TarData).take_front(511)));
EXPECT_FALSE(InputTarReader::isTarArchive(std::string(1024, '\0')));

std::string Script;
while (Script.size() < 1024)
Script += "SECTIONS { .text : { *(.text) } }\n";
EXPECT_FALSE(InputTarReader::isTarArchive(Script));
}
Loading