Skip to content

llvm_12.py

ofrak_patch_maker.toolchain.llvm_12

LLVM_12_0_1_Toolchain (Toolchain)

name: str property readonly

Returns:

Type Description
str

name property that matches the value used in toolchain.conf to access paths

_linker_script_flag: str private property readonly

Returns:

Type Description
str

the linker script flag for this toolchain, usually -T

_get_assembler_target(self, processor) private

Red Balloon Security strongly recommends all users provide their specific hardware target for best results.

Parameters:

Name Type Description Default
processor ProgramAttributes required

Returns:

Type Description
str

a default assembler target for the provided processor unless one is provided in self._config.

Exceptions:

Type Description
PatchMakerException

if no target provided and program attributes do not correspond to a default value.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def _get_assembler_target(self, processor: ProgramAttributes) -> str:
    arch = processor.isa.value
    if self._config.assembler_target:
        return self._config.assembler_target
    elif arch == InstructionSet.ARM.value:
        return "armv7-a"
    elif arch == InstructionSet.X86.value:
        return "generic64"
    else:
        raise ToolchainException("Assembler Target not provided and no valid default found!")

_get_compiler_target(self, processor) private

Returns a default compiler target for the provided processor unless one is provided in self._config.

Red Balloon Security strongly recommends all users provide their specific hardware target for best results.

Parameters:

Name Type Description Default
processor ProgramAttributes required

Returns:

Type Description
Optional[str]
Source code in ofrak_patch_maker/toolchain/llvm_12.py
def _get_compiler_target(self, processor: ProgramAttributes) -> Optional[str]:
    arch = processor.isa.value
    if self._config.compiler_target:
        return self._config.compiler_target
    if arch == InstructionSet.ARM.value:
        return "armv7---elf"
    elif arch == InstructionSet.X86.value:
        return "amd64---elf"
    else:
        raise ToolchainException("Compiler Target not provided and no valid default found!")

compile(self, c_file, header_dirs, out_dir='.')

Runs the Toolchain's C compiler on the input file.

Returns:

Type Description
str

path to the object file

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def compile(self, c_file: str, header_dirs: List[str], out_dir: str = ".") -> str:
    if self._config.userspace_dynamic_linker:
        out_file = os.path.join(out_dir, os.path.split(c_file)[-1] + ".o")
        # For now a complete override of the flags; we sidestep the clang front-end
        # in favor of a more userspace-friendly GNU configuration.
        self._execute_tool(
            self._compiler_path,
            ["-O3", "-Wall", "-g", "-fPIE", "-c"],
            [c_file] + ["-I" + x for x in header_dirs],
            out_file=out_file,
        )
        return os.path.abspath(out_file)
    else:
        return super().compile(c_file, header_dirs, out_dir=out_dir)

Run's the Toolchain's linker on the input object files.

Parameters:

Name Type Description Default
o_files List[str]

list of object files to be linked

required
exec_path str

path to executable output file

required
script

path to linker script (usually an .ld file)

None
Source code in ofrak_patch_maker/toolchain/llvm_12.py
def link(self, o_files: List[str], exec_path: str, script=None):
    if self._config.userspace_dynamic_linker:
        # We will ignore the script and any lld flags in this case
        flags = [
            f"--dynamic-linker={self._config.userspace_dynamic_linker}",
            f"-L{self._lib_path}",
        ]
        return self._execute_tool(self._linker_path, flags, o_files, out_file=exec_path)
    else:
        return super().link(o_files, exec_path, script=script)

_get_linker_map_flag(exec_path) private staticmethod

Generates the linker map file flag for a linker invocation given the executable path.

Parameters:

Name Type Description Default
exec_path str

path to executable

required

Returns:

Type Description

path to map file

Source code in ofrak_patch_maker/toolchain/llvm_12.py
@staticmethod
def _get_linker_map_flag(exec_path: str):
    return (f"--Map={exec_path}.map",)

add_linker_include_values(self, symbols, path)

Adds linker include entries to a provided file (usually ending in .inc).

For example GNU syntax prescribes PROVIDE(name = 0xdeadbeef);.

Parameters:

Name Type Description Default
symbols Mapping[str, int]

mapping of symbol string to effective address

required
path str

path to the provided linker include file.

required
Source code in ofrak_patch_maker/toolchain/llvm_12.py
def add_linker_include_values(self, symbols: Mapping[str, int], path: str):
    with open(path, "a") as f:
        for name, addr in symbols.items():
            if self.linker_include_filter(name):
                continue
            f.write(f"PROVIDE({name} = {hex(addr)});\n")

generate_linker_include_file(self, symbols, out_path)

This utility function receives the generated symbols dictionary that results from preprocessing a firmware image and generates a .inc file for use with linker scripts, enabling direct function calls when using the complete cross compilation toolchain.

This functionality must be defined for each toolchain given potential syntactical differences.

Parameters:

Name Type Description Default
symbols Mapping[str, int]

mappings of symbol string to effective address

required
out_path str

the path to the resulting symbol include file (usually .inc)

required

Returns:

Type Description
str

returns out_path

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def generate_linker_include_file(self, symbols: Mapping[str, int], out_path: str) -> str:
    with open(out_path, "w") as f:
        f.write(RBS_AUTOGEN_WARNING)

    self.add_linker_include_values(symbols, out_path)
    return out_path

ld_generate_region(self, object_path, segment_name, permissions, vm_address, length)

Generates regions for linker scripts.

Returns:

Type Description
Tuple[str, str]

a string entry for a "memory region" for the toolchain in question.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def ld_generate_region(
    self,
    object_path: str,
    segment_name: str,
    permissions: MemoryPermissions,
    vm_address: int,
    length: int,
) -> Tuple[str, str]:
    perms_string = self._ld_perm2str(permissions)
    stripped_seg_name = segment_name.strip(".")
    stripped_obj_name = os.path.basename(object_path).split(".")[0]
    region_name = f'".rbs_{stripped_obj_name}_{stripped_seg_name}_mem"'
    return (
        f"    {region_name} ({perms_string}) : ORIGIN = {hex(vm_address)}, LENGTH = {hex(length)}",
        region_name,
    )

ld_generate_bss_region(vm_address, length) staticmethod

Generates .bss regions for linker scripts.

Returns:

Type Description
Tuple[str, str]

a .bss memory entry string for the toolchain in question.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
@staticmethod
def ld_generate_bss_region(
    vm_address: int,
    length: int,
) -> Tuple[str, str]:
    region_name = ".bss_mem"
    perms_string = LLVM_12_0_1_Toolchain._ld_perm2str(MemoryPermissions.RW)
    return (
        f"    {region_name} ({perms_string}) : ORIGIN = {hex(vm_address)}, LENGTH = {hex(length)}",
        region_name,
    )

ld_generate_section(object_path, segment_name, memory_region_name) staticmethod

Generates sections for linker scripts.

Returns:

Type Description
str

a string entry for a "section" for the toolchain in question.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
@staticmethod
def ld_generate_section(
    object_path: str,
    segment_name: str,
    memory_region_name: str,
) -> str:
    stripped_seg_name = segment_name.strip(".")
    stripped_obj_name = os.path.basename(object_path).split(".")[0]
    abs_path = os.path.abspath(object_path)
    return (
        f"    .rbs_{stripped_obj_name}_{stripped_seg_name} : {{\n"
        f"        {abs_path}({segment_name})\n"
        f"    }} > {memory_region_name}"
    )

ld_generate_bss_section(memory_region_name) staticmethod

Generates .bss sections for linker scripts.

Returns:

Type Description
str

a .bss section entry string for the toolchain in question.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
@staticmethod
def ld_generate_bss_section(
    memory_region_name: str,
) -> str:
    bss_section_name = ".bss"
    return (
        f"    {bss_section_name} : {{\n"
        f"        *.o({bss_section_name})\n"
        f"    }} > {memory_region_name}"
    )

ld_script_create(self, name, memory_regions, sections, build_dir, symbol_files)

Constructs the linker script for the concrete toolchain class in use.

Uses the provided name, memory region strings, section strings, symbol files, expected entrypoint (if any) to generate a linker script that results in a valid FEM object when used within link.

Parameters:

Name Type Description Default
name str required
memory_regions List[str] required
sections List[str] required
build_dir str required
symbol_files List[str] required

Returns:

Type Description
str

path to the generated linker script

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def ld_script_create(
    self,
    name: str,
    memory_regions: List[str],
    sections: List[str],
    build_dir: str,
    symbol_files: List[str],
) -> str:
    _, ld_script_path = tempfile.mkstemp(dir=build_dir, prefix=name + "_", suffix=".ld")
    with open(ld_script_path, "w") as f:
        f.write(RBS_AUTOGEN_WARNING)
        for file in symbol_files:
            f.write(f"INCLUDE {str(os.path.abspath(file))}\n")

        f.write("\n\n")

        f.write("MEMORY\n{\n")
        for r in memory_regions:
            f.write(r + "\n")
        f.write("}\n")

        f.write("\n")

        f.write("SECTIONS\n{\n")
        for s in sections:
            f.write(s + "\n")
        f.write("\n")

        f.write("    /DISCARD/ : {\n")
        for d in self._linker_discard_list:
            f.write(f"        *({d})\n")
        f.write("    }\n")

        f.write("}\n")

    return ld_script_path

get_required_alignment(self, segment)

For example, x86 returns 16. This will most often be used when programmatically allocating memory for code/data.

Returns:

Type Description
int

required alignment factor for the toolchain/ISA

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def get_required_alignment(self, segment: Segment) -> int:
    # The linker will align function starts to 16-byte boundaries
    # https://patchwork.kernel.org/project/kernel-hardening/patch/[email protected]/
    # Plus, some other memory will also be aligned to 16
    # https://stackoverflow.com/a/49397524/2753454
    # Let's just do it for every section; we already allocated 16 extra earlier
    if self._processor.isa == InstructionSet.X86:
        return 16
    return 1

get_bin_file_symbols(self, executable_path)

For now, this utility only searches for global function and data symbols which are actually contained in a section in the file, as opposed to symbols which are referenced but undefined.

Parameters:

Name Type Description Default
executable_path str

path to the program to be analyzed for symbols

required

Returns:

Type Description
Dict[str, int]

mapping of symbol string to effective address.

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def get_bin_file_symbols(self, executable_path: str) -> Dict[str, int]:
    readobj_output = self._execute_tool(
        self._readobj_path, ["--symbols"], [executable_path], out_file=None
    )

    return self._parser.parse_symbols(readobj_output)

get_bin_file_segments(self, path)

Returns:

Type Description
Tuple[ofrak_patch_maker.toolchain.model.Segment, ...]

list of segments

Source code in ofrak_patch_maker/toolchain/llvm_12.py
def get_bin_file_segments(self, path: str) -> Tuple[Segment, ...]:
    """
    :return: list of segments
    """
    if get_file_format(path) != self.file_format:
        raise ToolchainException(
            "Extracted file format does not match this toolchain instance!"
        )

    readobj_output = self._execute_tool(
        self._readobj_path, ["--section-details"], [path], out_file=None
    )

    return self._parser.parse_sections(readobj_output)