tasign 0.2.0

TA ELF signing utilities with CMS/PKCS#7 support
//! 集成测试:`object` read→write 复制 ELF 后,增删 `.ta_signature` 是否与 [`tasign::build_plain_bin`] 语义一致。
//!
//! 逻辑改编自 [gimli-rs/object 的 objcopy 示例](https://github.com/gimli-rs/object/blob/master/crates/examples/src/objcopy.rs)。

use std::collections::HashMap;
use std::process;

use object::{
    write, Object, ObjectComdat, ObjectKind, ObjectSection, ObjectSymbol, RelocationTarget,
    SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolSection,
};
use tasign::{build_plain_bin, TA_SIGNATURE_SECTION};

static TRIVIAL_ELF64: &[u8] = include_bytes!("fixtures/trivial_elf64.o");

/// 仅 relocatable:与 object 示例一致,完整复制(跳过 Metadata 节)。
fn copy_relocatable(in_data: &[u8]) -> Vec<u8> {
    copy_relocatable_inner(
        in_data,
        CopyOptions {
            skip_ta_signature: false,
            append_ta_signature: None,
        },
    )
}

/// 复制时按节名跳过 `.ta_signature`(用于模拟「去掉签名节」后的 ELF)。
fn copy_relocatable_skip_ta_signature(in_data: &[u8]) -> Vec<u8> {
    copy_relocatable_inner(
        in_data,
        CopyOptions {
            skip_ta_signature: true,
            append_ta_signature: None,
        },
    )
}

/// 复制全部节后追加 `.ta_signature` 占位数据。
fn copy_then_append_ta_signature(in_data: &[u8], sig_placeholder: &[u8]) -> Vec<u8> {
    copy_relocatable_inner(
        in_data,
        CopyOptions {
            skip_ta_signature: false,
            append_ta_signature: Some(sig_placeholder),
        },
    )
}

struct CopyOptions<'a> {
    skip_ta_signature: bool,
    append_ta_signature: Option<&'a [u8]>,
}

fn copy_relocatable_inner(in_data: &[u8], opts: CopyOptions<'_>) -> Vec<u8> {
    let in_object = match object::File::parse(in_data) {
        Ok(object) => object,
        Err(err) => {
            eprintln!("Failed to parse file: {err}");
            process::exit(1);
        }
    };
    if in_object.kind() != ObjectKind::Relocatable {
        eprintln!("Unsupported object kind: {:?}", in_object.kind());
        process::exit(1);
    }

    let mut out_object = write::Object::new(
        in_object.format(),
        in_object.architecture(),
        in_object.endianness(),
    );
    out_object.mangling = write::Mangling::None;
    out_object.flags = in_object.flags();

    let mut out_sections: HashMap<object::SectionIndex, write::SectionId> = HashMap::new();

    for in_section in in_object.sections() {
        if in_section.kind() == SectionKind::Metadata {
            continue;
        }
        let name = in_section.name().unwrap_or("");
        if opts.skip_ta_signature && name == TA_SIGNATURE_SECTION {
            continue;
        }

        let section_id = out_object.add_section(
            in_section
                .segment_name()
                .unwrap()
                .unwrap_or("")
                .as_bytes()
                .to_vec(),
            in_section.name().unwrap().as_bytes().to_vec(),
            in_section.kind(),
        );
        let out_section = out_object.section_mut(section_id);
        if out_section.is_bss() {
            out_section.append_bss(in_section.size(), in_section.align());
        } else {
            out_section.set_data(in_section.data().unwrap(), in_section.align());
        }
        out_section.flags = in_section.flags();
        out_sections.insert(in_section.index(), section_id);
    }

    if let Some(data) = opts.append_ta_signature {
        let section_id = out_object.add_section(
            vec![],
            TA_SIGNATURE_SECTION.as_bytes().to_vec(),
            SectionKind::Data,
        );
        let out_section = out_object.section_mut(section_id);
        out_section.set_data(data, 1);
        out_section.flags = SectionFlags::Elf {
            sh_flags: (object::elf::SHF_ALLOC | object::elf::SHF_WRITE) as u64,
        };
    }

    let mut out_symbols: HashMap<object::SymbolIndex, write::SymbolId> = HashMap::new();
    for in_symbol in in_object.symbols() {
        let (section, value) = match in_symbol.section() {
            SymbolSection::None => (write::SymbolSection::None, in_symbol.address()),
            SymbolSection::Undefined => (write::SymbolSection::Undefined, in_symbol.address()),
            SymbolSection::Absolute => (write::SymbolSection::Absolute, in_symbol.address()),
            SymbolSection::Common => (write::SymbolSection::Common, in_symbol.address()),
            SymbolSection::Section(index) => {
                if let Some(out_section) = out_sections.get(&index) {
                    (
                        write::SymbolSection::Section(*out_section),
                        in_symbol.address() - in_object.section_by_index(index).unwrap().address(),
                    )
                } else {
                    assert_eq!(in_symbol.kind(), SymbolKind::Section);
                    continue;
                }
            }
            _ => panic!("unknown symbol section for {:?}", in_symbol),
        };
        let flags = match in_symbol.flags() {
            SymbolFlags::None => SymbolFlags::None,
            SymbolFlags::Elf { st_info, st_other } => SymbolFlags::Elf { st_info, st_other },
            SymbolFlags::MachO { n_desc } => SymbolFlags::MachO { n_desc },
            SymbolFlags::CoffSection {
                selection,
                associative_section,
            } => {
                let associative_section =
                    associative_section.map(|index| *out_sections.get(&index).unwrap());
                SymbolFlags::CoffSection {
                    selection,
                    associative_section,
                }
            }
            SymbolFlags::Xcoff {
                n_sclass,
                x_smtyp,
                x_smclas,
                containing_csect,
            } => {
                let containing_csect =
                    containing_csect.map(|index| *out_symbols.get(&index).unwrap());
                SymbolFlags::Xcoff {
                    n_sclass,
                    x_smtyp,
                    x_smclas,
                    containing_csect,
                }
            }
            _ => panic!("unknown symbol flags for {:?}", in_symbol),
        };
        let out_symbol = write::Symbol {
            name: in_symbol.name().unwrap_or("").as_bytes().to_vec(),
            value,
            size: in_symbol.size(),
            kind: in_symbol.kind(),
            scope: in_symbol.scope(),
            weak: in_symbol.is_weak(),
            section,
            flags,
        };
        let symbol_id = out_object.add_symbol(out_symbol);
        out_symbols.insert(in_symbol.index(), symbol_id);
    }

    for in_section in in_object.sections() {
        if in_section.kind() == SectionKind::Metadata {
            continue;
        }
        let name = in_section.name().unwrap_or("");
        if opts.skip_ta_signature && name == TA_SIGNATURE_SECTION {
            continue;
        }
        let out_section = *out_sections.get(&in_section.index()).unwrap();
        for (offset, in_relocation) in in_section.relocations() {
            let symbol = match in_relocation.target() {
                RelocationTarget::Symbol(symbol) => *out_symbols.get(&symbol).unwrap(),
                RelocationTarget::Section(section) => {
                    out_object.section_symbol(*out_sections.get(&section).unwrap())
                }
                _ => panic!("unknown relocation target for {:?}", in_relocation),
            };
            let out_relocation = write::Relocation {
                offset,
                symbol,
                addend: in_relocation.addend(),
                flags: in_relocation.flags(),
            };
            out_object
                .add_relocation(out_section, out_relocation)
                .unwrap();
        }
    }

    for in_comdat in in_object.comdats() {
        let mut sections = Vec::new();
        for in_section in in_comdat.sections() {
            sections.push(*out_sections.get(&in_section).unwrap());
        }
        out_object.add_comdat(write::Comdat {
            kind: in_comdat.kind(),
            symbol: *out_symbols.get(&in_comdat.symbol()).unwrap(),
            sections,
        });
    }

    if let Some(in_build_version) = match &in_object {
        object::File::MachO32(file) => file.build_version().unwrap(),
        object::File::MachO64(file) => file.build_version().unwrap(),
        _ => None,
    } {
        let mut out_build_version = object::write::MachOBuildVersion::default();
        out_build_version.platform = in_build_version.platform.get(in_object.endianness());
        out_build_version.minos = in_build_version.minos.get(in_object.endianness());
        out_build_version.sdk = in_build_version.sdk.get(in_object.endianness());
        out_object.set_macho_build_version(out_build_version);
    }

    out_object.write().unwrap()
}

#[test]
fn object_plain_roundtrip_add_then_strip_matches_unsigned() {
    let plain_unsigned = build_plain_bin(TRIVIAL_ELF64).expect("plain unsigned");
    let sig_placeholder = b"PKCS7_DER_PLACEHOLDER_MIN_LEN";
    let with_sig = copy_then_append_ta_signature(TRIVIAL_ELF64, sig_placeholder);
    let stripped = copy_relocatable_skip_ta_signature(&with_sig);
    let plain_after = build_plain_bin(&stripped).expect("stripped should have no .ta_signature");
    assert_eq!(
        plain_unsigned, plain_after,
        "object 增删 .ta_signature 后 plain 应与原始 unsigned 一致"
    );
}

#[test]
fn object_plain_roundtrip_copy_only_may_change_plain() {
    let plain_orig = build_plain_bin(TRIVIAL_ELF64).expect("plain");
    let copied = copy_relocatable(TRIVIAL_ELF64);
    match build_plain_bin(&copied) {
        Ok(plain_copy) if plain_copy == plain_orig => {}
        Ok(plain_copy) => {
            eprintln!(
                "object 仅复制(未增删节)后 plain 与原文不一致:orig_len={} copy_len={}",
                plain_orig.len(),
                plain_copy.len()
            );
        }
        Err(e) => {
            eprintln!("object 仅复制后 build_plain_bin 失败: {e:?}");
        }
    }
}