Skip to content
Closed
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
10 changes: 9 additions & 1 deletion .nanvix/z.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
_BUILD_OUTPUTS: list[str] = [
"libcrypto.a",
"libssl.a",
"libcrypto.so",
"libssl.so",
_TEST_ELF,
*(f"include/openssl/{h}" for h in _GENERATED_HEADERS),
]
Expand Down Expand Up @@ -270,10 +272,12 @@ def release(self) -> None:
repo = self.repo_root
libcrypto = repo / "libcrypto.a"
libssl = repo / "libssl.a"
libcrypto_so = repo / "libcrypto.so"
libssl_so = repo / "libssl.so"
headers_dir = repo / "include" / "openssl"
test_elf = repo / _TEST_ELF

for path in (libcrypto, libssl, headers_dir):
for path in (libcrypto, libssl, libcrypto_so, libssl_so, headers_dir):
if not path.exists():
log.fatal(
f"release: missing artefact {path}",
Expand All @@ -300,6 +304,8 @@ def release(self) -> None:
# Copy libraries.
shutil.copy2(libcrypto, sysroot / "lib" / "libcrypto.a")
shutil.copy2(libssl, sysroot / "lib" / "libssl.a")
shutil.copy2(libcrypto_so, sysroot / "lib" / "libcrypto.so")
shutil.copy2(libssl_so, sysroot / "lib" / "libssl.so")

# Copy headers.
for h in sorted(headers_dir.glob("*.h")):
Expand Down Expand Up @@ -441,6 +447,8 @@ def _verify_release(self, tarball: Path) -> None:
required = {
"sysroot/lib/libcrypto.a",
"sysroot/lib/libssl.a",
"sysroot/lib/libcrypto.so",
"sysroot/lib/libssl.so",
}
with tarfile.open(tarball, "r:gz") as tf:
members = set(tf.getnames())
Expand Down
157 changes: 153 additions & 4 deletions Makefile.nanvix
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,19 @@ EXE = .elf
TEST_SRC := openssl_nanvix_test.c
TEST_ELF := openssl_nanvix_test$(EXE)

# Shared library outputs. Linked from the static archives via
# `--whole-archive` after stripping the bss_log.o member that
# references unimplemented POSIX `openlog`/`syslog`/`closelog` (see
# Nanvix newlib does not implement the syslog client API; OpenSSL's
# BIO_s_log -- which we don't use -- is the only consumer).
LIBCRYPTO_SO := libcrypto.so
LIBSSL_SO := libssl.so

# Marker file produced by ./Configure.
CONFIGURED_MARKER := .nanvix-configured

_NANVIX_DOCKER_BUILD_GOALS := all build $(CONFIGURED_MARKER) $(TEST_ELF)
_NANVIX_DOCKER_BUILD_GOALS := all build $(CONFIGURED_MARKER) $(TEST_ELF) \
$(LIBCRYPTO_SO) $(LIBSSL_SO)
_NANVIX_GOALS := $(or $(MAKECMDGOALS),$(.DEFAULT_GOAL))

# Ensure required variables are defined.
Expand Down Expand Up @@ -90,9 +99,10 @@ CONFIGURE_OPTS = \
--openssldir="$(INSTALL_PREFIX)" \
--prefix="$(INSTALL_PREFIX)" \
nanvix \
no-shared \
shared \
no-pinshared \
enable-trace \
threads \
no-dso \
no-apps \
no-docs \
no-rdrand \
Expand All @@ -104,14 +114,63 @@ CONFIGURE_OPTS = \
# Build Targets
# ===========================================================================

all: build $(TEST_ELF)
all: build $(LIBCRYPTO_SO) $(LIBSSL_SO) $(TEST_ELF)

$(CONFIGURED_MARKER):
$(CONFIGURE_ENV) ./Configure $(CONFIGURE_OPTS)
touch $(CONFIGURED_MARKER)

build: $(CONFIGURED_MARKER)
$(MAKE) -j$$(nproc) all
@# Strip bss_log.o so consumers can use --whole-archive libcrypto.a.
@# bss_log.o is the implementation of OpenSSL's BIO_s_log -- a syslog
@# BIO backend that we don't use and that references the POSIX
@# `openlog`/`syslog`/`closelog` symbols Nanvix newlib does not
@# provide. Under normal archive selection nothing references
@# BIO_s_log so bss_log.o is never pulled; under --whole-archive
@# every member gets pulled and the link fails with three undefs.
@# Idempotent.
@if $(AR) t libcrypto.a 2>/dev/null | grep -q '^libcrypto-lib-bss_log\.o$$'; then \
echo " Stripping libcrypto-lib-bss_log.o from libcrypto.a"; \
$(AR) d libcrypto.a libcrypto-lib-bss_log.o; \
$(RANLIB) libcrypto.a; \
fi

# Build libcrypto.so from the (PIC-clean) static archive. OpenSSL's
# x86 sources are already position-independent, so no -fPIC at compile
# time is required. libposix/libc/libm symbols are left UND and bind
# at dlopen time against the host executable's .dynsym (same model
# already used by libxml2.so / libffi.so on Nanvix).
#
# `-lgcc` is required because OpenSSL's bignum / hash arithmetic uses
# 64-bit operations on 32-bit i686, and the resulting calls to libgcc
# helpers (__udivdi3, __umoddi3, ...) cannot be resolved against
# python.elf at dlopen time -- libgcc marks them hidden, so they are
# absent from python.elf's .dynsym. Embedding `-lgcc` ships those
# leaf helpers inside libcrypto.so. Only stateless arithmetic helpers
# are pulled in; the structural test below asserts that no stateful
# libgcc symbols (frame registry, unwinder, ifunc CPU dispatch state,
# emutls, ...) leak in.
$(LIBCRYPTO_SO): libcrypto.a
$(CC) -shared -fPIC -nostdlib \
-Wl,-soname,libcrypto.so -Wl,-z,noexecstack \
-Wl,--whole-archive libcrypto.a -Wl,--no-whole-archive \
-lgcc \
-o $@
@echo " OK: $@ ($$(wc -c < $@) bytes)"

# libssl.so depends on libcrypto.so via DT_NEEDED. Order matters:
# libcrypto.so must exist before libssl.so links, so the linker
# resolves -lcrypto against the .so rather than the .a (which would
# bundle libcrypto into libssl.so and defeat the deduplication).
$(LIBSSL_SO): libssl.a $(LIBCRYPTO_SO)
$(CC) -shared -fPIC -nostdlib \
-Wl,-soname,libssl.so -Wl,-z,noexecstack \
-Wl,--whole-archive libssl.a -Wl,--no-whole-archive \
-L. -lcrypto \
-lgcc \
-o $@
@echo " OK: $@ ($$(wc -c < $@) bytes)"

# Generate the inline test program source.
$(TEST_SRC):
Expand Down Expand Up @@ -146,13 +205,103 @@ $(TEST_SRC):
libcrypto.a libssl.a include/openssl/opensslv.h:
@echo " FAIL: $@ not found; run 'build' first"; exit 1

# Stateful libgcc symbols that MUST NOT be duplicated across DSOs in
# a static-only environment (Nanvix does not ship libgcc_s.so).
#
# NOTE: this check is a WORKAROUND. The long-term fix is to
# eliminate the libgcc dependency from our .so files entirely (by
# shipping libgcc_s.so or by re-exporting helpers from python.elf's
# .dynsym). Tracked in:
# nanvix-todo/eliminate-libgcc-dependency-in-shared-objects.md
#
# Three tiers, all enforced equally:
#
# Tier 1 -- correctness-critical (crashes / wrong unwind / wrong CPU
# dispatch / TLS aliasing / SjLj chain break):
# - Frame-registration registry (unwind-dw2-fde.c)
# - Core unwinder + _Unwind_Get*/Set* (unwind-dw2.c)
# - __gcc_personality_v0 / _sj0 (LSDA reader)
# - i386 CPU feature detection (__cpu_model / __cpu_features2 /
# __cpu_indicator_init)
# - emutls (emutls.c)
# - SjLj exception chain (unwind-sjlj.c)
#
# Tier 2 -- gcov profiling state (libgcov-driver.c). Not currently
# enabled on Nanvix, but listed to catch surprise enablement (e.g.
# someone flips on -fprofile-arcs for a benchmark build):
# - __gcov_master / __gcov_error_file / __gcov_kvp_dynamic_pool*
# - __gcov_{init,exit,dump,reset} mutators
# - (__gcov_root is intentionally per-DSO; NOT blocked)
#
# Tier 3 -- split-stack state (generic-morestack*.c). Not currently
# enabled (Nanvix uses fixed stacks), but listed for the same reason
# as Tier 2:
# - __morestack* / __generic_morestack* / __splitstack_*
#
# See nanvix-todo/libgcc-stateful-symbols-blocklist.md for the full
# audit and source citations.
LIBGCC_STATEFUL_RE := \
^(__register_frame(_info(_bases|_table(_bases)?)?|_table)?\
|__deregister_frame(_info(_bases)?)?\
|__frame_state_for\
|_Unwind_(Find_FDE|RaiseException|Resume(_or_Rethrow)?|ForcedUnwind\
|Backtrace|DeleteException|FindEnclosingFunction\
|Get(GR|IP(Info)?|CFA|LanguageSpecificData|RegionStart|TextRelBase|DataRelBase)\
|Set(GR|IP))\
|__gcc_personality_(v0|sj0)\
|__cpu_(model|indicator_init|features2)\
|__emutls_(get_address|register_common)\
|_Unwind_SjLj_(Register|Unregister|RaiseException|ForcedUnwind|Resume(_or_Rethrow)?)\
|__gcov_(init|exit|dump|reset|master|error_file|kvp_dynamic_pool(_index|_size)?)\
|__morestack(_block_signals|_unblock_signals|_fail|_release_segments\
|_load_mmap|_allocate_stack_space|_segments|_current_segment|_initial_sp)?\
|__generic_(morestack(_set_initial_sp)?|releasestack|findstack)\
|__splitstack_(find|block_signals|getcontext|setcontext|makecontext\
|resetcontext|releasecontext|block_signals_context|find_context)\
)$$

# Assert that a .so does not contain any Tier-1 stateful libgcc
# symbols (defined or undefined; we want zero references too,
# because resolving a stateful symbol against a different DSO's copy
# would still create the state-coherency hazard).
#
# $(1): path to the .so to check.
define check_no_stateful_libgcc
@echo " Checking $(1) for stateful libgcc symbols..."
@BAD_DEF=$$($(NANVIX_TOOLCHAIN)/bin/i686-nanvix-nm --defined-only $(1) 2>/dev/null \
| awk '{print $$NF}' | grep -E '$(LIBGCC_STATEFUL_RE)' || true); \
BAD_UND=$$($(NANVIX_TOOLCHAIN)/bin/i686-nanvix-nm --undefined-only $(1) 2>/dev/null \
| awk '{print $$NF}' | grep -E '$(LIBGCC_STATEFUL_RE)' || true); \
if [ -n "$$BAD_DEF" ]; then \
echo " FAIL: $(1) bundles stateful libgcc symbols (defined):"; \
echo "$$BAD_DEF" | sed 's/^/ /'; \
echo " These carry process-shared state and must not be"; \
echo " duplicated across DSOs. See"; \
echo " nanvix-todo/libgcc-stateful-symbols-blocklist.md"; \
exit 1; \
fi; \
if [ -n "$$BAD_UND" ]; then \
echo " FAIL: $(1) references stateful libgcc symbols (UND):"; \
echo "$$BAD_UND" | sed 's/^/ /'; \
echo " These would resolve at dlopen time against whichever"; \
echo " DSO loaded its own copy first, creating state-coherency"; \
echo " hazards. The fix is to export them from python.elf via"; \
echo " an explicit -Wl,-u + visibility=default wrapper, or to"; \
echo " remove the source dependency that pulled them in."; \
exit 1; \
fi; \
echo " OK: $(1) free of stateful libgcc symbols"
endef

$(TEST_ELF): $(TEST_SRC) libcrypto.a libssl.a include/openssl/opensslv.h
$(CC) -O2 \
-I$(CURDIR)/include \
$(TEST_SRC) \
-L$(CURDIR) -lssl -lcrypto \
$(NANVIX_LDFLAGS) $(NANVIX_LIBS) \
-o $@
$(call check_no_stateful_libgcc,$(LIBCRYPTO_SO))
$(call check_no_stateful_libgcc,$(LIBSSL_SO))

# ===========================================================================
# Clean
Expand Down