Skip to content

Commit 76671d7

Browse files
Merge pull request #208 from JonathonReinhart/207-bulk-audit
Perform RUNPATH auditing on all PyInstaller archive libraries before aborting
2 parents 0e22223 + 2937b11 commit 76671d7

File tree

11 files changed

+99
-38
lines changed

11 files changed

+99
-38
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [Unreleased]
6+
### Changed
7+
- Perform RUNPATH auditing on all PyInstaller archive libraries before aborting ([#208])
8+
9+
510
## [0.13.3] - 2021-10-14
611
### Fixed
712
- Fix ldd warning about libnssfix.so not being executable ([#204])
@@ -276,3 +281,4 @@ Initial release
276281
[#197]: https://github.com/JonathonReinhart/staticx/pull/197
277282
[#199]: https://github.com/JonathonReinhart/staticx/pull/199
278283
[#204]: https://github.com/JonathonReinhart/staticx/pull/204
284+
[#208]: https://github.com/JonathonReinhart/staticx/pull/208

staticx/errors.py

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class UnsupportedDynTagError(InvalidInputError):
3232
See https://github.com/JonathonReinhart/staticx/issues/172
3333
"""
3434
def __init__(self, libpath, value):
35+
self.libpath = libpath
36+
self.value = value
3537
super().__init__("{} uses unsupported {} ({!r}).\n"
3638
"See https://github.com/JonathonReinhart/staticx/issues/188"
3739
.format(libpath, self.tag, value))

staticx/hooks/pyinstaller.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import tempfile
44

55
from ..elf import get_shobj_deps, is_dynamic, LddError
6+
from ..errors import Error, UnsupportedRpathError, UnsupportedRunpathError
67
from ..utils import make_executable, mkdirs_for
78

89
def process_pyinstaller_archive(sx):
@@ -44,9 +45,15 @@ def __exit__(self, *exc_info):
4445

4546
def process(self):
4647
binaries = self._extract_binaries()
48+
49+
# These could be Python libraries, shared object dependencies, or
50+
# anything else a user might add via `binaries` in the .spec file.
51+
# Filter out everything except dynamic ELFs
52+
binaries = [b for b in binaries if is_dynamic(b)]
53+
54+
self._audit_libs(binaries)
55+
4756
for binary in binaries:
48-
# These could be Python libraries, shared object dependencies, or
49-
# anything else a user might add via `binaries` in the .spec file.
5057
self._add_required_deps(binary)
5158

5259

@@ -69,32 +76,41 @@ def _extract_binaries(self):
6976
with open(tmppath, 'wb') as f:
7077
f.write(data)
7178

79+
# Silence "you do not have execution permission" warning from ldd
80+
make_executable(tmppath)
81+
7282
# We can't use yield here, because we need all of the libraries to be
7383
# extracted prior to running ldd (see #61)
7484
result.append(tmppath)
7585

7686
return result
7787

88+
def _audit_libs(self, libs):
89+
"""Audit the dynamic libraries included in the PyInstaller archive"""
90+
errors = []
91+
for lib in libs:
92+
# Check for RPATH/RUNPATH, but only "dangerous" values and let
93+
# "harmless" values pass (e.g. "$ORIGIN/cffi.libs")
94+
try:
95+
self.sx.check_library_rpath(lib, dangerous_only=True)
96+
except (UnsupportedRpathError, UnsupportedRunpathError) as e:
97+
# Unfortunately, there's no easy way to fix an UnsupportedRunpathError
98+
# here, because staticx is not about to to modify the library and
99+
# re-pack the PyInstaller archive itself.
100+
errors.append(e)
101+
102+
if errors:
103+
msg = "Unsupported PyInstaller input\n\n"
104+
msg += "One or more libraries included in the PyInstaller"
105+
msg += " archive uses unsupported RPATH/RUNPATH tags:\n\n"
106+
for e in errors:
107+
msg += " {}: {}={!r}\n".format(e.libpath, e.tag, e.value)
108+
msg += "\nSee https://github.com/JonathonReinhart/staticx/issues/188"
109+
raise Error(msg)
78110

79111
def _add_required_deps(self, lib):
80112
"""Add dependencies of lib to staticx archive"""
81113

82-
# Verify this is a shared library
83-
if not is_dynamic(lib):
84-
# It's okay if there's a static executable in the PyInstaller
85-
# archive. See issue #78
86-
return
87-
88-
# Silence "you do not have execution permission" warning from ldd
89-
make_executable(lib)
90-
91-
# Check for RPATH/RUNPATH, but only "dangerous" values and let
92-
# "harmless" values pass (e.g. "$ORIGIN/cffi.libs")
93-
self.sx.check_library_rpath(lib, dangerous_only=True)
94-
# Unfortunately, there's no easy way to fix an UnsupportedRunpathError
95-
# here, because staticx is not about to to modify the library and
96-
# re-pack the PyInstaller archive itself.
97-
98114
# Try to get any dependencies of this file
99115
try:
100116
deps = get_shobj_deps(lib, libpath=[self.tmpdir.name])

test/pyinstall-lib-runpath/app.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44

55
mydir = Path(__file__).parent
66

7-
dll = CDLL(mydir / 'shlib.so')
7+
libfoo = CDLL(mydir / 'libfoo.so')
8+
libbar = CDLL(mydir / 'libbar.so')
89

9-
dll.foo.argtypes = ()
10-
dll.foo.restype = c_int
10+
def setup_prototype(func, restype, *argtypes):
11+
func.restype = restype
12+
func.argtypes = argtypes
1113

12-
print("foo() =>", dll.foo())
14+
setup_prototype(libfoo.foo, c_int)
15+
setup_prototype(libbar.bar, c_int)
16+
17+
print("foo() =>", libfoo.foo())
18+
print("bar() =>", libbar.bar())

test/pyinstall-lib-runpath/app.spec

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# PyInstaller spec file
22

3-
binaries = [('shlib.so', '.')]
3+
binaries = [
4+
('libfoo.so', '.'),
5+
('libbar.so', '.'),
6+
]
47

58
a = Analysis(['app.py'], binaries=binaries)
69

test/pyinstall-lib-runpath/libbar.c

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "libbar.h"
2+
3+
int bar(void)
4+
{
5+
return 88;
6+
}

test/pyinstall-lib-runpath/libbar.h

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef LIBBAR_DOT_H
2+
#define LIBBAR_DOT_H
3+
4+
int bar(void);
5+
6+
#endif /* LIBBAR_DOT_H */

test/pyinstall-lib-runpath/shlib.c test/pyinstall-lib-runpath/libfoo.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "shlib.h"
1+
#include "libfoo.h"
22

33
int foo(void)
44
{

test/pyinstall-lib-runpath/libfoo.h

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef LIBFOO_DOT_H
2+
#define LIBFOO_DOT_H
3+
4+
int foo(void);
5+
6+
#endif /* LIBFOO_DOT_H */

test/pyinstall-lib-runpath/run_test.sh

+24-8
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ app="./dist/app"
1111
outfile="./dist/app.staticx"
1212

1313
# Build the shared library
14-
gcc -Wall -Werror -fPIC -c -o shlib.o shlib.c
15-
gcc -shared -Wl,-rpath=/bogus/absolute/path -Wl,--enable-new-dtags -o shlib.so shlib.o
16-
verify_uses_runpath shlib.so
14+
function make_shlib() {
15+
lib="$1"
16+
gcc -Wall -Werror -fPIC -c -o $lib.o $lib.c
17+
gcc -shared -Wl,-rpath=/bogus/absolute/path -Wl,--enable-new-dtags -o $lib.so $lib.o
18+
verify_uses_runpath $lib.so
19+
}
20+
21+
make_shlib libfoo
22+
make_shlib libbar
1723

1824
# Run the application normally
1925
echo -e "\nPython app run normally:"
@@ -35,9 +41,19 @@ if output=$(staticx $STATICX_FLAGS $app $outfile); then
3541
echo "FAIL: Staticx permitted a problematic library using RUNPATH"
3642
exit 66
3743
fi
38-
if ! (echo "$output" | grep -q "unsupported DT_RUNPATH"); then
39-
echo "FAIL: Unexpected output text:"
40-
echo "$output"
41-
exit 66
42-
fi
44+
45+
function check_output() {
46+
pat="$1"
47+
if ! (echo "$output" | grep -q "$pat"); then
48+
echo "FAIL: Unexpected output text (missing \"$pat\"):"
49+
echo "-----------------------------------------------------------------"
50+
echo "$output"
51+
echo "-----------------------------------------------------------------"
52+
exit 66
53+
fi
54+
}
55+
check_output "Unsupported PyInstaller input"
56+
check_output "libfoo.so.*DT_RUNPATH"
57+
check_output "libbar.so.*DT_RUNPATH"
58+
4359
echo "Success"

test/pyinstall-lib-runpath/shlib.h

-6
This file was deleted.

0 commit comments

Comments
 (0)