Verify Integration
Static Verification of Integration
Protected binaries include a .txtrp section. Starting in RunSafe Protect 5.16.0 and Stainless 2.1.2, packages also ship verify_lfr.py to check that section (and related markers) for you. On older releases, use the manual readelf check below.
Using verify_lfr.py (5.16.0 / Stainless 2.1.2 and later)
verify_lfr.py is a Python 3 script that checks whether ELF binaries were built with RunSafe Protect. It treats a binary as protected if it contains a .txtrp section, or (when that section is absent) if it links against liblfr.so.
On Linux installations the script is at:
/usr/bin/alkemist/lfr/scripts/verify_lfr.py
The script depends on the host file, readelf, and ldd utilities.
Check a single binary
python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py ./hello_world
When protection was applied successfully:
$ python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py ./hello_world
./hello_world is protected
When protection was not applied, the script prints that the binary is not protected and exits with status 1:
$ python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py ./hello_world
./hello_world is not protected
Check a directory of binaries
Pass a directory to recursively scan for ELF binaries (object files and non-ELF files are skipped):
python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py /path/to/install/tree
Example output:
$ python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py /path/to/install/tree
/path/to/install/tree/bin/hello_world is protected.
/path/to/install/tree/bin/unprotected_tool not protected and not excluded.
Protected or excluded: 1
Not Protected and not excluded: 1
Percentage expected: 50.0
At least one binary wasn't protected. Examine your build setup for lfr-helper
If any binary is unprotected and not listed in an exclusion file, the script exits with status 1.
Exclude known-unprotected binaries
When scanning a full install tree or root filesystem, some binaries may intentionally remain unprotected. Pass -i / --input_file with a text file of basenames to exclude (one per line):
python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py -i exclude.txt /path/to/install/tree
Example exclude.txt:
ld-linux-x86-64.so.2
bash
Excluded unprotected binaries are reported as excluded and counted toward the protected-or-excluded total.
Custom readelf path
For Android or cross-toolchain checks, point the script at a specific readelf with -r / --readelf_path:
python3 /usr/bin/alkemist/lfr/scripts/verify_lfr.py \
-r /path/to/toolchain/bin/llvm-readelf \
./hello_world
Manual check with readelf (before 5.16.0 / Stainless 2.1.2)
If verify_lfr.py is not installed (RunSafe Protect before 5.16.0, or Stainless before 2.1.2), use readelf to confirm the .txtrp section exists and has content:
readelf -x .txtrp <binary name> | grep 0x -m3
For example, with a hello_world binary built with RunSafe Protect:
$ readelf -x .txtrp ./hello_world | grep 0x -m3
0x0000200e 01d10b00 4065f0ff ffffffff ff020000 ....@e..........
0x0000201e 0609027c 09047c16 046bf0ff ffffffff ...|..|..k......
0x0000202e ff020000 0c0d2a7c 072a7c07 2a7c0504 ......*|.*|.*|..
If RunSafe Protect was not successfully applied, readelf reports that the section does not exist:
readelf: Warning: Section '.txtrp' was not dumped because it does not exist!
Runtime Verification of Integration
Most deployments of RunSafe Protect have the ability to produce debug logging to see protection being applied by setting the
LFR_DEBUG_LEVEL environment variable to a value from 1-10. When set to 1, users can verify that randomization is occurring
by inspecting the debug output looking for Finished randomizing.
$ LFR_DEBUG_LEVEL=1 ./hello_world
Module@0x7ffca1049a30 base:0x5582e3148000 GOT:0x5582e314c000 .eh_frame_hdr:0x5582e314a110 static:0
Module path:'./hello_world'
Checking for restricted context in lfr.toml
LFR SELinux: CANT read /sys/fs/selinux/
LFR SELinux: Disabled
Module@0x7ffca1049920 base:0x5582e3148000 GOT:0x5582e314c000 .eh_frame_hdr:0x5582e314a110 static:0
Module path:'./hello_world'
Module@0x7ffca1049920 sec@0x5582e3149070[400] TRaP@0x5582e314a012[251]
GOT relocations found: 5
Found exec section @0x5582e3149070[400]
Building functions...
Shuffling functions...
Divcode size:379 Old: 400
Module Name: ./hello_world Module trampoline_counter:0
Divcode@0x7f23523a4000
New init:0x5582e3149000 entry:0x5582e3149202
.eh_frame_hdr found 6 entries
Updating Eh_frame (Mod args 0x7ffca1049c80, dynamic 0x5582e314bde0)
Calling unmap on 0x7f23523a4000 which is size 379
Finished randomizing
Hello, world!
Troubleshooting Integration Issues
If static or runtime verification do not show successful integration, there are a few common causes.
Missing lfr-helper
When verify_lfr.py reports that a binary is not protected, or when readelf cannot find a .txtrp section, make sure that lfr-helper was applied to the step of the build process that produces that binary.
Absolute Paths to Build Tools
If lfr-helper is present, inspect your build script/system to see if the compiler, linker, or other build tools are invoked using absolute paths (i.e. /usr/bin/gcc) instead of by name (gcc) and change the invocation to use the build tool name instead of an absolute path to the tool.
If that is not feasible due to other build system constraints, reach out to support@runsafesecurity.com and we can walk you through some simple build system tweaks that will allow you to continue to use absolute paths.
Build System Cleans $PATH
Some build systems sanitize or override the PATH variable, which can break the lfr-helper integration. In those cases, modify your build system to add /usr/bin/alkemist/lfr/scripts to the front of your PATH variable.
Build System Overrides CFLAGS/CXXFLAGS/LDFLAGS
Some build systems hardcode CFLAGS, CXXFLAGS, or LDFLAGS (or similar). In those cases, add the following flags to any environment variables that set the build flags:
-ffunction-sections -fdata-sections -fPIC -B/usr/bin/alkemist/lfr/scripts