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
109 changes: 73 additions & 36 deletions packages/emacs/fixrand.c
Original file line number Diff line number Diff line change
@@ -1,60 +1,97 @@
/*
* fixrand.c — LD_PRELOAD shim for deterministic builds.
*
* Intercepts getrandom() and /dev/urandom reads to return deterministic
* output from a fixed-seed LCG. This makes Emacs's internal hash table
* seeding reproducible, fixing .elc and .pdmp non-determinism.
*
* Inspired by libfate (Nicolas Graves / Guix).
*
* Build: gcc -shared -fPIC -O2 -ldl -o fixrand.so fixrand.c
* Usage: LD_PRELOAD=./fixrand.so emacs --batch ...
*/
* fixrand.c — LD_PRELOAD shim for deterministic builds.
*
* Intercepts getrandom() and /dev/urandom reads to return deterministic
* output from a fixed-seed LCG. This makes Emacs's internal hash table
* seeding reproducible, fixing .elc and .pdmp non-determinism.
*
* Also freezes clock_gettime(CLOCK_REALTIME) to SOURCE_DATE_EPOCH: the dumped
* .pdmp otherwise embeds two wall-clock reads that getrandom interception does
* not cover — the *scratch* buffer's buffer-display-time (Fcurrent_time during
* make_initial_frame) and Vgc_elapsed GC timing (src/alloc.c).
*
* Inspired by libfate (Nicolas Graves / Guix).
*
* Build: gcc -shared -fPIC -O2 -ldl -o fixrand.so fixrand.c
* Usage: LD_PRELOAD=./fixrand.so emacs --batch ...
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <time.h>
#include <unistd.h>

static uint32_t seed = 0x12345678;

static uint32_t next_rand(void) {
seed = seed * 1103515245 + 12345;
return seed;
seed = seed * 1103515245 + 12345;
return seed;
}

static void fill_deterministic(unsigned char *buf, size_t len) {
for (size_t i = 0; i < len; i++)
buf[i] = next_rand() & 0xFF;
for (size_t i = 0; i < len; i++)
buf[i] = next_rand() & 0xFF;
}

ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) {
(void)flags;
if (!buf) { errno = EFAULT; return -1; }
fill_deterministic(buf, buflen);
return (ssize_t)buflen;
(void)flags;
if (!buf) { errno = EFAULT; return -1; }
fill_deterministic(buf, buflen);
return (ssize_t)buflen;
}

ssize_t read(int fd, void *buf, size_t count) {
static ssize_t (*real_read)(int, void *, size_t) = NULL;
if (!real_read)
real_read = dlsym(RTLD_NEXT, "read");
static ssize_t (*real_read)(int, void *, size_t) = NULL;
if (!real_read)
real_read = dlsym(RTLD_NEXT, "read");

char proc_path[64];
char target[256];
snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd);
ssize_t len = readlink(proc_path, target, sizeof(target) - 1);
if (len > 0) {
target[len] = '\0';
if (strcmp(target, "/dev/urandom") == 0 ||
strcmp(target, "/dev/random") == 0) {
if (!buf) { errno = EFAULT; return -1; }
fill_deterministic(buf, count);
return (ssize_t)count;
}
}
return real_read(fd, buf, count);
char proc_path[64];
char target[256];
snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd);
ssize_t len = readlink(proc_path, target, sizeof(target) - 1);
if (len > 0) {
target[len] = '\0';
if (strcmp(target, "/dev/urandom") == 0 ||
strcmp(target, "/dev/random") == 0) {
if (!buf) { errno = EFAULT; return -1; }
fill_deterministic(buf, count);
return (ssize_t)count;
}
}
return real_read(fd, buf, count);
}

/* Freeze wall-clock reads so Emacs's current-time (e.g. the *scratch* buffer's
buffer-display-time, set in make_initial_frame) and GC timing (Vgc_elapsed)
are deterministic in the dumped .pdmp. Only CLOCK_REALTIME is pinned (to the
sandbox-exported SOURCE_DATE_EPOCH); CLOCK_MONOTONIC and others pass through
so timeouts / progress loops are unaffected. */
int clock_gettime(clockid_t clk_id, struct timespec *tp) {
static int (*real_clock_gettime)(clockid_t, struct timespec *) = NULL;
if (!real_clock_gettime)
real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime");
if (clk_id == CLOCK_REALTIME && tp) {
const char *e = getenv("SOURCE_DATE_EPOCH");
if (e && *e != '\0') {
char *endptr;
errno = 0;
long long val = strtoll(e, &endptr, 10);
if (errno == 0 && *endptr == '\0') {
tp->tv_sec = (time_t)val;
} else {
/* Malformed SOURCE_DATE_EPOCH — fall back to epoch 0 for determinism */
tp->tv_sec = 0;
}
} else {
tp->tv_sec = 0;
}
tp->tv_nsec = 0;
return 0;
}
return real_clock_gettime(clk_id, tp);
}
32 changes: 25 additions & 7 deletions packages/ghostscript/build.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,37 @@ set -e
rm -rf freetype lcms2mt jpeg libpng openjpeg

case $(uname -m) in
x86_64) MARCH="-march=x86-64-v3" ;;
aarch64) MARCH="-march=armv8-a" ;;
*) MARCH="" ;;
x86_64) MARCH="-march=x86-64-v3" ;;
aarch64) MARCH="-march=armv8-a" ;;
*) MARCH="" ;;
esac
export CFLAGS="$MARCH -O2 -pipe -gno-record-gcc-switches -ffile-prefix-map=$(pwd)=/builddir"
export LDFLAGS="-Wl,--build-id=none"
export CXXFLAGS="${CFLAGS}"

# Reproducibility: mkromfs/pack_ps read SOURCE_DATE_EPOCH but then do
# `if (!buildtime) buildtime = time(NULL)`, which treats the sandbox's
# SOURCE_DATE_EPOCH=0 (epoch 0 is falsy) as "unset" and falls back to wall-clock
# time -> non-deterministic gs_romfs_buildtime baked into the gs binary. Only
# fall back to time() when SOURCE_DATE_EPOCH is genuinely unset.
#
# Guard: verify the pattern exists before patching. If upstream renames the
# variable or changes the logic, fail the build loudly rather than silently
# producing a non-reproducible binary.
for src in base/mkromfs.c base/pack_ps.c; do
if ! grep -q 'if (!buildtime)' "$src"; then
echo "ERROR: expected pattern 'if (!buildtime)' not found in $src" >&2
echo "Upstream may have changed the SOURCE_DATE_EPOCH handling — please update the sed patch." >&2
exit 1
fi
done
sed -i 's/if (!buildtime)/if (!env_source_date_epoch)/' base/mkromfs.c base/pack_ps.c

./configure --prefix=/usr \
--disable-static \
--with-system-libtiff \
--disable-compiler-inits \
CFLAGS="${CFLAGS:--g -O3} -fPIC"
--disable-static \
--with-system-libtiff \
--disable-compiler-inits \
CFLAGS="${CFLAGS:--g -O3} -fPIC"

make -j$(nproc)

Expand Down
64 changes: 64 additions & 0 deletions packages/haskell-language-server/build.ncl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputLib, Source, Test, .. } = import "minimal.ncl" in

let base = import "../base/build.ncl" in
let cabal = import "../cabal/build.ncl" in
let ghc = import "../ghc/build.ncl" in
let glibc = import "../glibc/build.ncl" in

let version = "2.14.0.0" in

{
name = "haskell-language-server",

build_deps = [
{ file = "build.sh" } | Local,
{
url = "https://github.com/haskell/haskell-language-server/archive/refs/tags/%{version}.tar.gz",
sha256 = "02fdd2ea8048cddce0872f78fcc4ba15558f751333c97d24b9abadac8ee27dcb",
extract = true,
strip_prefix = "haskell-language-server-%{version}",
} | Source,
base,
ghc,
cabal,
],

runtime_deps = [glibc],

needs =
{
dns = {},
internet = {},
} | Needs,

cmd = "./build.sh",

build_args = {
include version,
},

outputs = {
hls = { glob = "usr/bin/haskell-language-server" } | OutputBin,
libs = { glob = "usr/lib/*.so*" } | OutputLib,
},

attrs =
{
upstream_version = version,
source_provenance = {
category = 'GithubRepo,
owner = "haskell",
repo = "haskell-language-server",
},
build_cost_multiple = 4,
} | Attrs,

tests = {
version_check =
{
class = 'Standalone,
test_deps = [],
cmds = [["haskell-language-server", "--version"]],
} | Test,
},
} | BuildSpec
69 changes: 69 additions & 0 deletions packages/haskell-language-server/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/bash
set -euo pipefail

# The source tarball is already extracted with strip_prefix, so we're in the source root

# Build HLS for the GHC version available in the sandbox
export GHC="$(command -v ghc)"
export CABAL="$(command -v cabal)"

# Update cabal package index
cabal update

# Reproducibility: strip embedded build paths from GHC-produced object files and
# suppress the linker's random build-id. These are the Haskell analogues of
# -ffile-prefix-map and -Wl,--build-id=none for C (see AGENTS.md §Reproducibility).
# -optc flags reach the C compiler GHC invokes for C stubs / Cmm; -optl reaches ld.
GHC_REPRO_OPTS="-optc-ffile-prefix-map=$(pwd)=/builddir -optl-Wl,--build-id=none"

# Build HLS with the available GHC version
cabal build \
--ghc-options="-j$(nproc) $GHC_REPRO_OPTS" \
exe:haskell-language-server

# Install to OUTPUT_DIR
mkdir -p "$OUTPUT_DIR"/usr/bin
mkdir -p "$OUTPUT_DIR"/usr/lib

# Find and copy the built binary from cabal's build directory
HLS_BIN=$(cabal list-bin exe:haskell-language-server)
cp "$HLS_BIN" "$OUTPUT_DIR"/usr/bin/

# Strip the linker build-id from the installed binary (cabal may re-link without
# our -optl flag for the final exe depending on version; belt-and-suspenders).
strip --remove-section=.note.gnu.build-id "$OUTPUT_DIR"/usr/bin/haskell-language-server 2>/dev/null || true

# Copy only Haskell-specific shared libraries the binary depends on.
# Filter out core system libraries (libc, libm, libpthread, ld-linux, etc.)
# which are provided by the host's glibc and would conflict if bundled.
for lib in $(ldd "$HLS_BIN" | grep '\.so' | awk '{print $3}'); do
if [ -n "$lib" ] && [ -f "$lib" ]; then
case "$lib" in
/usr/lib/ghc/*|*/cabal/store/*|*/dist-newstyle/*)
cp "$lib" "$OUTPUT_DIR"/usr/lib/
;;
/lib/x86_64-linux-gnu/lib[cm].so*|/lib/aarch64-linux-gnu/lib[cm].so*)
;; # skip core C runtime
/lib/*/libpthread*|/lib/*/libdl*|/lib/*/librt*|/lib/*/libresolv*)
;; # skip POSIX threading/dl/rt
/lib/*/ld-linux*|/lib*/ld-linux*)
;; # skip dynamic linker
/usr/lib/*/libgmp*|/usr/lib/*/libffi*|/usr/lib/*/libnuma*)
# GHC runtime dependencies — include these
cp "$lib" "$OUTPUT_DIR"/usr/lib/
;;
*)
# For any other library, include it only if it's NOT in the base
# system library directories (heuristic: skip /lib/ and /usr/lib/
# top-level, include everything from package-specific paths).
case "$lib" in
/lib/*|/usr/lib/x86_64-linux-gnu/*|/usr/lib/aarch64-linux-gnu/*)
;; # skip generic system library
*)
cp "$lib" "$OUTPUT_DIR"/usr/lib/
;;
esac
;;
esac
fi
done
13 changes: 13 additions & 0 deletions packages/unzip/build.ncl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
let { standaloneTest, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in
let base = import "../base/build.ncl" in
let make = import "../make/build.ncl" in
let patch = import "../patch/build.ncl" in
let toolchain = import "../toolchain/build.ncl" in
let glibc = import "../glibc/build.ncl" in

Expand All @@ -15,8 +16,20 @@ let version = "6.0" in
extract = true,
strip_prefix = "unzip60",
} | Source,
# Info-ZIP unzip 6.0 is frozen (2009); its CVE fixes live only in distro
# patch-sets. Apply Debian's full 6.0-29 series (build.sh) to fix
# CVE-2014-8139/8140/8141/9636/9913, CVE-2015-7696/7697, CVE-2016-9844,
# CVE-2018-1000035, CVE-2019-13232, CVE-2022-0529/0530 + build fixes. We
# stay on Info-ZIP (not bsdunzip) because consumers like bun's build invoke
# `unzip -DD`, an Info-ZIP-only flag bsdunzip lacks.
{
url = "gs://minimal-staging-archives/unzip-debian-patches-6.0-29.tar.xz",
sha256 = "afb1c7eb9c2671f0b9b27e2db2f211ff131b404b25c64efd9cb048e2f9fa755c",
extract = true,
} | Source,
base,
make,
patch,
toolchain,
],
runtime_deps = [
Expand Down
13 changes: 10 additions & 3 deletions packages/unzip/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ case $(uname -m) in
*) MARCH="" ;;
esac

# unzip 6.0 (2009) declares gmtime()/localtime() in K&R style, which
# modern GCC rejects as conflicting with <time.h>'s prototypes.
sed -i 's|^ struct tm \*gmtime(), \*localtime();|/* & */|' unix/unxcfg.h
# Info-ZIP unzip 6.0 is frozen (2009); its CVE fixes ship only as distro
# patches. Apply Debian's full 6.0-29 series in order — fixes CVE-2014-8139/
# 8140/8141/9636/9913, CVE-2015-7696/7697, CVE-2016-9844, CVE-2018-1000035,
# CVE-2019-13232, CVE-2022-0529/0530, plus build fixes (incl. patch 30, which
# drops the K&R gmtime()/localtime() declarations that modern GCC rejects — this
# replaces the manual sed that used to live here).
PATCHES=unzip-debian-patches-6.0-29
while IFS= read -r p; do
[ -n "$p" ] && patch -p1 -i "$PATCHES/$p"
done < "$PATCHES/series"

# Bypass unix/Makefile's autoconfigure (its feature probes misbehave on
# modern glibc and incorrectly set NO_DIR). Build unzips directly with
Expand Down