diff options
Diffstat (limited to 'libexec')
| -rw-r--r-- | libexec/Makefile | 5 | ||||
| -rw-r--r-- | libexec/atf/atf-sh/Makefile | 1 | ||||
| -rwxr-xr-x | libexec/nuageinit/nuageinit | 2 | ||||
| -rw-r--r-- | libexec/nuageinit/tests/nuageinit.sh | 8 | ||||
| -rw-r--r-- | libexec/pkg-serve/Makefile | 9 | ||||
| -rw-r--r-- | libexec/pkg-serve/pkg-serve.8 | 107 | ||||
| -rw-r--r-- | libexec/pkg-serve/pkg-serve.c | 180 | ||||
| -rw-r--r-- | libexec/pkg-serve/tests/Makefile | 5 | ||||
| -rw-r--r-- | libexec/pkg-serve/tests/pkg_serve_test.sh | 230 | ||||
| -rw-r--r-- | libexec/rc/rc.conf | 2 | ||||
| -rw-r--r-- | libexec/rc/rc.d/Makefile | 9 | ||||
| -rwxr-xr-x | libexec/rc/rc.d/NETWORKING | 2 | ||||
| -rw-r--r-- | libexec/rc/rc.d/virtual_oss | 6 | ||||
| -rw-r--r-- | libexec/rc/safe_eval.sh | 13 | ||||
| -rw-r--r-- | libexec/rtld-elf/aarch64/reloc.c | 2 | ||||
| -rw-r--r-- | libexec/rtld-elf/rtld.c | 254 | ||||
| -rw-r--r-- | libexec/rtld-elf/tests/Makefile | 2 | ||||
| -rw-r--r-- | libexec/rtld-elf/tests/dlopen_hash_test.c | 45 | ||||
| -rw-r--r-- | libexec/rtld-elf/tests/set_var_test.c | 38 |
19 files changed, 863 insertions, 57 deletions
diff --git a/libexec/Makefile b/libexec/Makefile index 180dd10b5d29..a5e3ea655a9e 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -14,6 +14,7 @@ SUBDIR= ${_atf} \ ${_makewhatis.local} \ ${_mknetid} \ ${_phttpget} \ + ${_pkgserve} \ ${_pppoed} \ rc \ revnetgroup \ @@ -65,6 +66,10 @@ _dma= dma _hyperv+= hyperv .endif +.if ${MK_PKGSERVE} != "no" +_pkgserve= pkg-serve +.endif + .if ${MK_NIS} != "no" _mknetid= mknetid _ypxfr= ypxfr diff --git a/libexec/atf/atf-sh/Makefile b/libexec/atf/atf-sh/Makefile index afd848581f36..a76b59e9a1aa 100644 --- a/libexec/atf/atf-sh/Makefile +++ b/libexec/atf/atf-sh/Makefile @@ -71,7 +71,6 @@ FILESGROUPS= SUBR SUBRDIR= ${SHAREDIR}/atf SUBR= libatf-sh.subr -SUBRTAGS= package=tests HAS_TESTS= SUBDIR.${MK_TESTS}+= tests diff --git a/libexec/nuageinit/nuageinit b/libexec/nuageinit/nuageinit index f7700f7d8e70..a1ebd3f52b25 100755 --- a/libexec/nuageinit/nuageinit +++ b/libexec/nuageinit/nuageinit @@ -727,7 +727,7 @@ local function load_userdata() f:close() local obj = nil - if ud then + if ud and line == "#cloud-config" then f = io.open(ni_path .. "/" .. ud) obj = yaml.load(f:read("*a")) f:close() diff --git a/libexec/nuageinit/tests/nuageinit.sh b/libexec/nuageinit/tests/nuageinit.sh index 3a01413f8487..1fd68d5a178e 100644 --- a/libexec/nuageinit/tests/nuageinit.sh +++ b/libexec/nuageinit/tests/nuageinit.sh @@ -76,10 +76,12 @@ nocloud_userdata_script_body() { mkdir -p media/nuageinit printf "instance-id: iid-local01\n" > "${PWD}"/media/nuageinit/meta-data - printf "#!/bin/sh\necho yeah\n" > "${PWD}"/media/nuageinit/user-data - chmod 755 "${PWD}"/media/nuageinit/user-data + # ensure this is an invalid when parsed with the yaml parser + printf "#!/bin/sh\n: ${test:-yes}\necho $test\n" > "${PWD}"/media/nuageinit/user-data + chmod 644 "${PWD}"/media/nuageinit/user-data atf_check -s exit:0 /usr/libexec/nuageinit "${PWD}"/media/nuageinit nocloud - atf_check -o inline:"#!/bin/sh\necho yeah\n" cat var/cache/nuageinit/user_data + atf_check test -x var/cache/nuageinit/user_data + atf_check -o inline:"#!/bin/sh\n: ${test:-yes}\necho $test\n" cat var/cache/nuageinit/user_data } nocloud_user_data_script_body() diff --git a/libexec/pkg-serve/Makefile b/libexec/pkg-serve/Makefile new file mode 100644 index 000000000000..3ac6bf34be14 --- /dev/null +++ b/libexec/pkg-serve/Makefile @@ -0,0 +1,9 @@ +.include <src.opts.mk> + +PROG= pkg-serve +MAN= pkg-serve.8 +BINDIR= /usr/libexec + +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.prog.mk> diff --git a/libexec/pkg-serve/pkg-serve.8 b/libexec/pkg-serve/pkg-serve.8 new file mode 100644 index 000000000000..b51daff029db --- /dev/null +++ b/libexec/pkg-serve/pkg-serve.8 @@ -0,0 +1,107 @@ +.\" Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org> +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd March 17, 2026 +.Dt PKG-SERVE 8 +.Os +.Sh NAME +.Nm pkg-serve +.Nd serve pkg repositories over TCP via inetd +.Sh SYNOPSIS +.Nm +.Ar basedir +.Sh DESCRIPTION +The +.Nm +utility serves +.Xr pkg 8 +repositories using the pkg TCP protocol. +It is designed to be invoked by +.Xr inetd 8 +and communicates via standard input and output. +.Pp +The +.Ar basedir +argument specifies the root directory containing the package repositories. +All file requests are resolved relative to this directory. +.Pp +On startup, +.Nm +enters a Capsicum sandbox, restricting filesystem access to +.Ar basedir . +.Sh PROTOCOL +The protocol is line-oriented. +Upon connection, the server sends a greeting: +.Bd -literal -offset indent +ok: pkg-serve <version> +.Ed +.Pp +The client may then issue commands: +.Bl -tag -width "get file age" +.It Ic get Ar file age +Request a file. +If the file's modification time is newer than +.Ar age +(a Unix timestamp), the server responds with: +.Bd -literal -offset indent +ok: <size> +.Ed +.Pp +followed by +.Ar size +bytes of file data. +If the file has not been modified, the server responds with: +.Bd -literal -offset indent +ok: 0 +.Ed +.Pp +On error, the server responds with: +.Bd -literal -offset indent +ko: <error message> +.Ed +.It Ic quit +Close the connection. +.El +.Sh INETD CONFIGURATION +Add the following line to +.Xr inetd.conf 5 : +.Bd -literal -offset indent +pkg stream tcp nowait nobody /usr/libexec/pkg-serve pkg-serve /usr/local/poudriere/data/packages +.Ed +.Pp +And define the service in +.Xr services 5 : +.Bd -literal -offset indent +pkg 62000/tcp +.Ed +.Sh REPOSITORY CONFIGURATION +On the client side, configure a repository in +.Pa /usr/local/etc/pkg/repos/myrepo.conf +to use the +.Ic tcp:// +scheme: +.Bd -literal -offset indent +myrepo: { + url: "tcp://pkgserver.example.com:62000/myrepo", +} +.Ed +.Pp +The path component of the URL is resolved relative to the +.Ar basedir +given to +.Nm . +For example, if +.Nm +is started with +.Pa /usr/local/poudriere/data/packages +as +.Ar basedir , +the above configuration will serve files from +.Pa /usr/local/poudriere/data/packages/myrepo/ . +.Sh SEE ALSO +.Xr inetd 8 , +.Xr inetd.conf 5 , +.Xr pkg 8 +.Sh AUTHORS +.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org diff --git a/libexec/pkg-serve/pkg-serve.c b/libexec/pkg-serve/pkg-serve.c new file mode 100644 index 000000000000..56770ef37f88 --- /dev/null +++ b/libexec/pkg-serve/pkg-serve.c @@ -0,0 +1,180 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org> + */ + +/* + * Speaks the same protocol as "pkg ssh" (see pkg-ssh(8)): + * -> ok: pkg-serve <version> + * <- get <file> <mtime> + * -> ok: <size>\n<data> or ok: 0\n or ko: <error>\n + * <- quit + */ + +#include <sys/capsicum.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define VERSION "0.1" +#define BUFSZ 32768 + +static void +usage(void) +{ + fprintf(stderr, "usage: pkg-serve basedir\n"); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + struct stat st; + cap_rights_t rights; + char *line = NULL; + char *file, *age; + size_t linecap = 0, r, toread; + ssize_t linelen; + off_t remaining; + time_t mtime; + char *end; + int fd, ffd; + char buf[BUFSZ]; + const char *basedir; + + if (argc != 2) + usage(); + + basedir = argv[1]; + + if ((fd = open(basedir, O_DIRECTORY | O_RDONLY | O_CLOEXEC)) < 0) + err(EXIT_FAILURE, "open(%s)", basedir); + + cap_rights_init(&rights, CAP_READ, CAP_FSTATAT, CAP_LOOKUP, + CAP_FCNTL); + if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS) + err(EXIT_FAILURE, "cap_rights_limit"); + + if (cap_enter() < 0 && errno != ENOSYS) + err(EXIT_FAILURE, "cap_enter"); + + printf("ok: pkg-serve " VERSION "\n"); + fflush(stdout); + + while ((linelen = getline(&line, &linecap, stdin)) > 0) { + /* trim newline */ + if (linelen > 0 && line[linelen - 1] == '\n') + line[--linelen] = '\0'; + + if (linelen == 0) + continue; + + if (strcmp(line, "quit") == 0) + break; + + if (strncmp(line, "get ", 4) != 0) { + printf("ko: unknown command '%s'\n", line); + fflush(stdout); + continue; + } + + file = line + 4; + + if (*file == '\0') { + printf("ko: bad command get, expecting 'get file age'\n"); + fflush(stdout); + continue; + } + + /* skip leading slash */ + if (*file == '/') + file++; + + /* find the age argument */ + age = file; + while (*age != '\0' && !isspace((unsigned char)*age)) + age++; + + if (*age == '\0') { + printf("ko: bad command get, expecting 'get file age'\n"); + fflush(stdout); + continue; + } + + *age++ = '\0'; + + /* skip whitespace */ + while (isspace((unsigned char)*age)) + age++; + + if (*age == '\0') { + printf("ko: bad command get, expecting 'get file age'\n"); + fflush(stdout); + continue; + } + + errno = 0; + mtime = (time_t)strtoimax(age, &end, 10); + if (errno != 0 || *end != '\0' || end == age) { + printf("ko: bad number %s\n", age); + fflush(stdout); + continue; + } + + if (fstatat(fd, file, &st, AT_RESOLVE_BENEATH) == -1) { + printf("ko: file not found\n"); + fflush(stdout); + continue; + } + + if (!S_ISREG(st.st_mode)) { + printf("ko: not a file\n"); + fflush(stdout); + continue; + } + + if (st.st_mtime <= mtime) { + printf("ok: 0\n"); + fflush(stdout); + continue; + } + + if ((ffd = openat(fd, file, O_RDONLY | O_RESOLVE_BENEATH)) == -1) { + printf("ko: file not found\n"); + fflush(stdout); + continue; + } + + printf("ok: %" PRIdMAX "\n", (intmax_t)st.st_size); + fflush(stdout); + + remaining = st.st_size; + while (remaining > 0) { + toread = sizeof(buf); + if ((off_t)toread > remaining) + toread = (size_t)remaining; + r = read(ffd, buf, toread); + if (r <= 0) + break; + if (fwrite(buf, 1, r, stdout) != r) + break; + remaining -= r; + } + close(ffd); + if (remaining > 0) + errx(EXIT_FAILURE, "%s: file truncated during transfer", + file); + fflush(stdout); + } + + return (EXIT_SUCCESS); +} diff --git a/libexec/pkg-serve/tests/Makefile b/libexec/pkg-serve/tests/Makefile new file mode 100644 index 000000000000..9c62cf855f83 --- /dev/null +++ b/libexec/pkg-serve/tests/Makefile @@ -0,0 +1,5 @@ +PACKAGE= tests + +ATF_TESTS_SH= pkg_serve_test + +.include <bsd.test.mk> diff --git a/libexec/pkg-serve/tests/pkg_serve_test.sh b/libexec/pkg-serve/tests/pkg_serve_test.sh new file mode 100644 index 000000000000..fc9cf2101369 --- /dev/null +++ b/libexec/pkg-serve/tests/pkg_serve_test.sh @@ -0,0 +1,230 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org> + +PKG_SERVE="${PKG_SERVE:-/usr/libexec/pkg-serve}" + +serve() +{ + printf "$1" | "${PKG_SERVE}" "$2" +} + +check_output() +{ + local pattern="$1" ; shift + output=$(serve "$@") + case "$output" in + *${pattern}*) + return 0 + ;; + *) + echo "Expected pattern: ${pattern}" + echo "Got: ${output}" + return 1 + ;; + esac +} + +atf_test_case greeting +greeting_head() +{ + atf_set "descr" "Server sends greeting on connect" +} +greeting_body() +{ + mkdir repo + check_output "ok: pkg-serve " "quit\n" repo || + atf_fail "greeting not found" +} + +atf_test_case unknown_command +unknown_command_head() +{ + atf_set "descr" "Unknown commands get ko response" +} +unknown_command_body() +{ + mkdir repo + check_output "ko: unknown command 'plop'" "plop\nquit\n" repo || + atf_fail "expected ko for unknown command" +} + +atf_test_case get_missing_file +get_missing_file_head() +{ + atf_set "descr" "Requesting a missing file returns ko" +} +get_missing_file_body() +{ + mkdir repo + check_output "ko: file not found" "get nonexistent.pkg 0\nquit\n" repo || + atf_fail "expected file not found" +} + +atf_test_case get_file +get_file_head() +{ + atf_set "descr" "Requesting an existing file returns its content" +} +get_file_body() +{ + mkdir repo + echo "testcontent" > repo/test.pkg + output=$(serve "get test.pkg 0\nquit\n" repo) + echo "$output" | grep -q "ok: 12" || + atf_fail "expected ok: 12, got: ${output}" + echo "$output" | grep -q "testcontent" || + atf_fail "expected testcontent in output" +} + +atf_test_case get_file_leading_slash +get_file_leading_slash_head() +{ + atf_set "descr" "Leading slash in path is stripped" +} +get_file_leading_slash_body() +{ + mkdir repo + echo "testcontent" > repo/test.pkg + check_output "ok: 12" "get /test.pkg 0\nquit\n" repo || + atf_fail "leading slash not stripped" +} + +atf_test_case get_file_uptodate +get_file_uptodate_head() +{ + atf_set "descr" "File with old mtime returns ok: 0" +} +get_file_uptodate_body() +{ + mkdir repo + echo "testcontent" > repo/test.pkg + check_output "ok: 0" "get test.pkg 9999999999\nquit\n" repo || + atf_fail "expected ok: 0 for up-to-date file" +} + +atf_test_case get_directory +get_directory_head() +{ + atf_set "descr" "Requesting a directory returns ko" +} +get_directory_body() +{ + mkdir -p repo/subdir + check_output "ko: not a file" "get subdir 0\nquit\n" repo || + atf_fail "expected not a file" +} + +atf_test_case get_missing_age +get_missing_age_head() +{ + atf_set "descr" "get without age argument returns error" +} +get_missing_age_body() +{ + mkdir repo + check_output "ko: bad command get" "get test.pkg\nquit\n" repo || + atf_fail "expected bad command get" +} + +atf_test_case get_bad_age +get_bad_age_head() +{ + atf_set "descr" "get with non-numeric age returns error" +} +get_bad_age_body() +{ + mkdir repo + check_output "ko: bad number" "get test.pkg notanumber\nquit\n" repo || + atf_fail "expected bad number" +} + +atf_test_case get_empty_arg +get_empty_arg_head() +{ + atf_set "descr" "get with no arguments returns error" +} +get_empty_arg_body() +{ + mkdir repo + check_output "ko: bad command get" "get \nquit\n" repo || + atf_fail "expected bad command get" +} + +atf_test_case path_traversal +path_traversal_head() +{ + atf_set "descr" "Path traversal with .. is rejected" +} +path_traversal_body() +{ + mkdir repo + check_output "ko: file not found" \ + "get ../etc/passwd 0\nquit\n" repo || + atf_fail "path traversal not rejected" +} + +atf_test_case get_subdir_file +get_subdir_file_head() +{ + atf_set "descr" "Files in subdirectories are served" +} +get_subdir_file_body() +{ + mkdir -p repo/sub + echo "subcontent" > repo/sub/file.pkg + output=$(serve "get sub/file.pkg 0\nquit\n" repo) + echo "$output" | grep -q "ok: 11" || + atf_fail "expected ok: 11, got: ${output}" + echo "$output" | grep -q "subcontent" || + atf_fail "expected subcontent in output" +} + +atf_test_case multiple_gets +multiple_gets_head() +{ + atf_set "descr" "Multiple get commands in one session" +} +multiple_gets_body() +{ + mkdir repo + echo "aaa" > repo/a.pkg + echo "bbb" > repo/b.pkg + output=$(serve "get a.pkg 0\nget b.pkg 0\nquit\n" repo) + echo "$output" | grep -q "ok: 4" || + atf_fail "expected ok: 4 for a.pkg" + echo "$output" | grep -q "aaa" || + atf_fail "expected content of a.pkg" + echo "$output" | grep -q "bbb" || + atf_fail "expected content of b.pkg" +} + +atf_test_case bad_basedir +bad_basedir_head() +{ + atf_set "descr" "Non-existent basedir causes exit failure" +} +bad_basedir_body() +{ + atf_check -s not-exit:0 -e match:"open" \ + "${PKG_SERVE}" /nonexistent/path +} + +atf_init_test_cases() +{ + atf_add_test_case greeting + atf_add_test_case unknown_command + atf_add_test_case get_missing_file + atf_add_test_case get_file + atf_add_test_case get_file_leading_slash + atf_add_test_case get_file_uptodate + atf_add_test_case get_directory + atf_add_test_case get_missing_age + atf_add_test_case get_bad_age + atf_add_test_case get_empty_arg + atf_add_test_case path_traversal + atf_add_test_case get_subdir_file + atf_add_test_case multiple_gets + atf_add_test_case bad_basedir +} diff --git a/libexec/rc/rc.conf b/libexec/rc/rc.conf index c4cc6dea02a2..75420e42cdeb 100644 --- a/libexec/rc/rc.conf +++ b/libexec/rc/rc.conf @@ -791,7 +791,7 @@ if [ -z "${source_rc_confs_defined}" ]; then } fi -# Allow vendors to override FreeBSD defaults in /etc/default/rc.conf +# Allow vendors to override FreeBSD defaults in /etc/defaults/rc.conf # without the need to carefully manage /etc/rc.conf. if [ -r /etc/defaults/vendor.conf ]; then . /etc/defaults/vendor.conf diff --git a/libexec/rc/rc.d/Makefile b/libexec/rc/rc.d/Makefile index f25338c68766..cb94380aff32 100644 --- a/libexec/rc/rc.d/Makefile +++ b/libexec/rc/rc.d/Makefile @@ -88,15 +88,18 @@ ACCT+= utx .endif .endif +.if ${MACHINE} == "i386" || ${MACHINE} == "amd64" || \ + ${MACHINE} == "arm64" CONFGROUPS.${MK_ACPI}+= ACPI ACPIPACKAGE= acpi ACPI= power_profile +.endif +.if ${MACHINE} == "i386" CONFGROUPS.${MK_APM}+= APM APMPACKAGE= apm -APM= apm -.if ${MACHINE} == "i386" -APM+= apmd +APM= apm \ + apmd .endif CONFGROUPS.${MK_AUDIT}+= AUDIT diff --git a/libexec/rc/rc.d/NETWORKING b/libexec/rc/rc.d/NETWORKING index 402e20927a4c..8f46e78e3426 100755 --- a/libexec/rc/rc.d/NETWORKING +++ b/libexec/rc/rc.d/NETWORKING @@ -2,7 +2,7 @@ # # -# PROVIDE: NETWORKING NETWORK +# PROVIDE: NETWORKING # REQUIRE: netif netwait netoptions routing ppp ipfw stf # REQUIRE: defaultroute route6d resolv bridge # REQUIRE: static_arp static_ndp diff --git a/libexec/rc/rc.d/virtual_oss b/libexec/rc/rc.d/virtual_oss index d55b51463442..a25abf256f55 100644 --- a/libexec/rc/rc.d/virtual_oss +++ b/libexec/rc/rc.d/virtual_oss @@ -25,6 +25,10 @@ required_modules="cuse" configs= pidpath="/var/run/${name}" default_unit=$(sysctl -n hw.snd.default_unit 2> /dev/null) + +# Default configuration's control device. +: "${virtual_oss_default_control_device:="vdsp.ctl"}" + virtual_oss_default_args="\ -S \ -C 2 \ @@ -35,7 +39,7 @@ virtual_oss_default_args="\ -i 8 \ -f /dev/dsp${default_unit} \ -d dsp \ - -t vdsp.ctl" + -t ${virtual_oss_default_control_device}" # Set to NO by default. Set it to "YES" to enable virtual_oss. : "${virtual_oss_enable:="NO"}" diff --git a/libexec/rc/safe_eval.sh b/libexec/rc/safe_eval.sh index 6c23b4c98218..3b3241ae821d 100644 --- a/libexec/rc/safe_eval.sh +++ b/libexec/rc/safe_eval.sh @@ -1,8 +1,8 @@ : # RCSid: -# $Id: safe_eval.sh,v 1.25 2025/08/07 22:13:03 sjg Exp $ +# $Id: safe_eval.sh,v 1.28 2026/04/22 16:36:32 sjg Exp $ # -# @(#) Copyright (c) 2023-2024 Simon J. Gerraty +# @(#) Copyright (c) 2023-2026 Simon J. Gerraty # # SPDX-License-Identifier: BSD-2-Clause # @@ -23,13 +23,16 @@ else fi ## -# safe_set +# safe_set [xtras] # # return a safe variable setting -# any non-alphanumeric chars are replaced with '_' +# any non-alphanumeric chars other than those in "xtras" +# will be replaced with '_' # +# "xtras" should be used with caution and cannot include ';' +# safe_set() { - ${SED:-sed} 's/[ ]*#.*//;/^[A-Za-z_][A-Za-z0-9_]*=/!d;s;[^A-Za-z0-9_. "$,/=:+-];_;g' + ${SED:-sed} 's/^[ ]*//;s/[ ]*#.*//;s/^:.*//;/^[A-Za-z_][A-Za-z0-9_]*=/!d;s;[^A-Za-z0-9_. "'"$1"'$,/=:+-];_;g' } ## diff --git a/libexec/rtld-elf/aarch64/reloc.c b/libexec/rtld-elf/aarch64/reloc.c index 85f7c1b4022a..96ee13048d81 100644 --- a/libexec/rtld-elf/aarch64/reloc.c +++ b/libexec/rtld-elf/aarch64/reloc.c @@ -44,8 +44,6 @@ void *_rtld_tlsdesc_static(void *); void *_rtld_tlsdesc_undef(void *); void *_rtld_tlsdesc_dynamic(void *); -void _exit(int); - bool arch_digest_dynamic(struct Struct_Obj_Entry *obj, const Elf_Dyn *dynp) { diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index 5e15ba996ec8..29984e40b574 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -178,6 +178,7 @@ static int symlook_obj1_sysv(SymLook *, const Obj_Entry *); static int symlook_obj1_gnu(SymLook *, const Obj_Entry *); static void *tls_get_addr_slow(struct tcb *, int, size_t, bool) __noinline; static void trace_loaded_objects(Obj_Entry *, bool); +static int try_fds_open(const char *name, const char *path); static void unlink_object(Obj_Entry *); static void unload_object(Obj_Entry *, RtldLockState *lockstate); static void unref_dag(Obj_Entry *); @@ -187,6 +188,10 @@ static char *origin_subst_one(Obj_Entry *, char *, const char *, const char *, static char *origin_subst(Obj_Entry *, const char *); static bool obj_resolve_origin(Obj_Entry *obj); static void preinit_main(void); +static void rtld_recalc_bind_not(const char *); +static void rtld_recalc_dangerous_ld_env(void); +static void rtld_recalc_debug(const char *); +static void rtld_recalc_path_rpath(const char *); static int rtld_verify_versions(const Objlist *); static int rtld_verify_object_versions(Obj_Entry *); static void object_add_name(Obj_Entry *, const char *); @@ -198,6 +203,17 @@ static uint32_t gnu_hash(const char *); static bool matched_symbol(SymLook *, const Obj_Entry *, Sym_Match_Result *, const unsigned long); +struct ld_env_var_desc; +static void rtld_set_var_bind_not(struct ld_env_var_desc *lvd); +static void rtld_set_var_bind_now(struct ld_env_var_desc *lvd); +static void rtld_set_var_debug(struct ld_env_var_desc *lvd); +static void rtld_set_var_dynamic_weak(struct ld_env_var_desc *lvd); +static void rtld_set_var_libmap_disable(struct ld_env_var_desc *lvd); +static void rtld_set_var_library_path(struct ld_env_var_desc *lvd); +static void rtld_set_var_library_path_fds(struct ld_env_var_desc *lvd); +static void rtld_set_var_library_path_rpath(struct ld_env_var_desc *lvd); +static void rtld_set_var_loadfltr(struct ld_env_var_desc *lvd); + void r_debug_state(struct r_debug *, struct link_map *) __noinline __exported; void _r_debug_postinit(struct link_map *) __noinline __exported; @@ -215,7 +231,6 @@ static bool dangerous_ld_env; /* True if environment variables have been used to affect the libraries loaded */ bool ld_bind_not; /* Disable PLT update */ static const char *ld_bind_now; /* Environment variable for immediate binding */ -static const char *ld_debug; /* Environment variable for debugging */ static bool ld_dynamic_weak = true; /* True if non-weak definition overrides weak definition */ static const char *ld_library_path; /* Environment variable for search path */ @@ -368,26 +383,35 @@ struct ld_env_var_desc { const char *val; const bool unsecure : 1; const bool can_update : 1; - const bool debug : 1; bool owned : 1; + void (*const on_update)(struct ld_env_var_desc *); }; #define LD_ENV_DESC(var, unsec, ...) \ [LD_##var] = { .n = #var, .unsecure = unsec, __VA_ARGS__ } static struct ld_env_var_desc ld_env_vars[] = { - LD_ENV_DESC(BIND_NOW, false), + LD_ENV_DESC(BIND_NOW, false, .can_update = true, + .on_update = rtld_set_var_bind_now), LD_ENV_DESC(PRELOAD, true), LD_ENV_DESC(LIBMAP, true), - LD_ENV_DESC(LIBRARY_PATH, true, .can_update = true), - LD_ENV_DESC(LIBRARY_PATH_FDS, true, .can_update = true), - LD_ENV_DESC(LIBMAP_DISABLE, true), - LD_ENV_DESC(BIND_NOT, true), - LD_ENV_DESC(DEBUG, true, .can_update = true, .debug = true), + LD_ENV_DESC(LIBRARY_PATH, true, .can_update = true, + .on_update = rtld_set_var_library_path), + LD_ENV_DESC(LIBRARY_PATH_FDS, true, .can_update = true, + .on_update = rtld_set_var_library_path_fds), + LD_ENV_DESC(LIBMAP_DISABLE, true, .can_update = true, + .on_update = rtld_set_var_libmap_disable), + LD_ENV_DESC(BIND_NOT, true, .can_update = true, + .on_update = rtld_set_var_bind_not), + LD_ENV_DESC(DEBUG, true, .can_update = true, + .on_update = rtld_set_var_debug), LD_ENV_DESC(ELF_HINTS_PATH, true), - LD_ENV_DESC(LOADFLTR, true), - LD_ENV_DESC(LIBRARY_PATH_RPATH, true, .can_update = true), + LD_ENV_DESC(LOADFLTR, true, .can_update = true, + .on_update = rtld_set_var_loadfltr), + LD_ENV_DESC(LIBRARY_PATH_RPATH, true, .can_update = true, + .on_update = rtld_set_var_library_path_rpath), LD_ENV_DESC(PRELOAD_FDS, true), - LD_ENV_DESC(DYNAMIC_WEAK, true, .can_update = true), + LD_ENV_DESC(DYNAMIC_WEAK, true, .can_update = true, + .on_update = rtld_set_var_dynamic_weak), LD_ENV_DESC(TRACE_LOADED_OBJECTS, false), LD_ENV_DESC(UTRACE, false, .can_update = true), LD_ENV_DESC(DUMP_REL_PRE, false, .can_update = true), @@ -516,7 +540,7 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) struct stat st; Elf_Addr *argcp; char **argv, **env, **envp, *kexecpath; - const char *argv0, *binpath, *library_path_rpath, *static_tls_extra; + const char *argv0, *binpath, *static_tls_extra; struct ld_env_var_desc *lvd; caddr_t imgentry; char buf[MAXPATHLEN]; @@ -721,9 +745,8 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) } } - ld_debug = ld_get_env_var(LD_DEBUG); - if (ld_bind_now == NULL) - ld_bind_not = ld_get_env_var(LD_BIND_NOT) != NULL; + rtld_recalc_debug(ld_get_env_var(LD_DEBUG)); + rtld_recalc_bind_not(ld_get_env_var(LD_BIND_NOT)); ld_dynamic_weak = ld_get_env_var(LD_DYNAMIC_WEAK) == NULL; libmap_disable = ld_get_env_var(LD_LIBMAP_DISABLE) != NULL; libmap_override = ld_get_env_var(LD_LIBMAP); @@ -733,31 +756,18 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp) ld_preload_fds = ld_get_env_var(LD_PRELOAD_FDS); ld_elf_hints_path = ld_get_env_var(LD_ELF_HINTS_PATH); ld_loadfltr = ld_get_env_var(LD_LOADFLTR) != NULL; - library_path_rpath = ld_get_env_var(LD_LIBRARY_PATH_RPATH); - if (library_path_rpath != NULL) { - if (library_path_rpath[0] == 'y' || - library_path_rpath[0] == 'Y' || - library_path_rpath[0] == '1') - ld_library_path_rpath = true; - else - ld_library_path_rpath = false; - } + rtld_recalc_path_rpath(ld_get_env_var(LD_LIBRARY_PATH_RPATH)); static_tls_extra = ld_get_env_var(LD_STATIC_TLS_EXTRA); if (static_tls_extra != NULL && static_tls_extra[0] != '\0') { sz = parse_integer(static_tls_extra); if (sz >= RTLD_STATIC_TLS_EXTRA && sz <= SIZE_T_MAX) ld_static_tls_extra = sz; } - dangerous_ld_env = libmap_disable || libmap_override != NULL || - ld_library_path != NULL || ld_preload != NULL || - ld_elf_hints_path != NULL || ld_loadfltr || !ld_dynamic_weak || - static_tls_extra != NULL; + rtld_recalc_dangerous_ld_env(); ld_tracing = ld_get_env_var(LD_TRACE_LOADED_OBJECTS); ld_utrace = ld_get_env_var(LD_UTRACE); set_ld_elf_hints_path(); - if (ld_debug != NULL && *ld_debug != '\0') - debug = 1; dbg("%s is initialized, base address = %p", __progname, (caddr_t)aux_info[AT_BASE]->a_un.a_ptr); dbg("RTLD dynamic = %p", obj_rtld.dynamic); @@ -2644,6 +2654,11 @@ initlist_add_objects(Obj_Entry *obj, Obj_Entry *tail, Objlist *list, initlist_add_neededs(obj->needed_aux_filtees, NULL, iflist); objlist_push_tail(iflist, obj); + + /* Recursively process the successor objects. */ + nobj = globallist_next(obj); + if (nobj != NULL && obj != tail) + initlist_add_objects(nobj, tail, list, iflist); } else { if (obj->init_scanned) return; @@ -2866,9 +2881,12 @@ load_object(const char *name, int fd_u, const Obj_Entry *refobj, int flags) * using stat(). */ if ((fd = open(path, O_RDONLY | O_CLOEXEC | O_VERIFY)) == -1) { - _rtld_error("Cannot open \"%s\"", path); - free(path); - return (NULL); + fd = try_fds_open(path, ld_library_dirs); + if (fd == -1) { + _rtld_error("Cannot open \"%s\"", path); + free(path); + return (NULL); + } } } else { fd = fcntl(fd_u, F_DUPFD_CLOEXEC, 0); @@ -3578,6 +3596,53 @@ rtld_nop_exit(void) } /* + * Parse string of the format '#number/name", where number must be a + * decimal number of the opened file descriptor listed in + * LD_LIBRARY_PATH_FDS. If successful, tries to open dso name under + * dirfd number and returns resulting fd. + * On any error, returns -1. + */ +static int +try_fds_open(const char *name, const char *path) +{ + const char *n; + char *envcopy, *fdstr, *last_token, *ncopy; + size_t len; + int fd, dirfd, dirfd_path; + + if (!trust || name[0] != '#' || path == NULL) + return (-1); + + name++; + n = strchr(name, '/'); + if (n == NULL) + return (-1); + len = n - name; + ncopy = xmalloc(len + 1); + memcpy(ncopy, name, len); + ncopy[len] = '\0'; + dirfd = parse_integer(ncopy); + free(ncopy); + if (dirfd == -1) + return (-1); + + envcopy = xstrdup(path); + dirfd_path = -1; + for (fdstr = strtok_r(envcopy, ":", &last_token); fdstr != NULL; + fdstr = strtok_r(NULL, ":", &last_token)) { + dirfd_path = parse_integer(fdstr); + if (dirfd_path == dirfd) + break; + } + free(envcopy); + if (dirfd_path != dirfd) + return (-1); + + fd = __sys_openat(dirfd, n + 1, O_RDONLY | O_CLOEXEC | O_VERIFY); + return (fd); +} + +/* * Iterate over a search path, translate each element, and invoke the * callback on the result. */ @@ -6483,7 +6548,11 @@ parse_integer(const char *str) if (c < '0' || c > '9') return (-1); + if (n > INT_MAX / RADIX) + return (-1); n *= RADIX; + if (n > INT_MAX - (c - '0')) + return (-1); n += c - '0'; } @@ -6611,18 +6680,121 @@ rtld_get_var(const char *name) return (NULL); } +static void +rtld_recalc_dangerous_ld_env(void) +{ + /* + * Never reset dangerous_ld_env back to false if rtld was ever + * contaminated with it set to true. + */ + dangerous_ld_env |= libmap_disable || libmap_override != NULL || + ld_library_path != NULL || ld_preload != NULL || + ld_elf_hints_path != NULL || ld_loadfltr || !ld_dynamic_weak || + ld_get_env_var(LD_STATIC_TLS_EXTRA) != NULL; +} + +static void +rtld_recalc_debug(const char *ld_debug) +{ + if (ld_debug != NULL && *ld_debug != '\0') + debug = 1; +} + +static void +rtld_set_var_debug(struct ld_env_var_desc *lvd) +{ + rtld_recalc_debug(lvd->val); +} + +static void +rtld_set_var_library_path(struct ld_env_var_desc *lvd) +{ + ld_library_path = lvd->val; +} + +static void +rtld_set_var_library_path_fds(struct ld_env_var_desc *lvd) +{ + ld_library_dirs = lvd->val; +} + +static void +rtld_recalc_path_rpath(const char *library_path_rpath) +{ + if (library_path_rpath != NULL) { + if (library_path_rpath[0] == 'y' || + library_path_rpath[0] == 'Y' || + library_path_rpath[0] == '1') + ld_library_path_rpath = true; + else + ld_library_path_rpath = false; + } else { + ld_library_path_rpath = false; + } +} + +static void +rtld_set_var_library_path_rpath(struct ld_env_var_desc *lvd) +{ + rtld_recalc_path_rpath(lvd->val); +} + +static void +rtld_recalc_bind_not(const char *bind_not_val) +{ + if (ld_bind_now == NULL) + ld_bind_not = bind_not_val != NULL; +} + +static void +rtld_set_var_bind_now(struct ld_env_var_desc *lvd) +{ + ld_bind_now = lvd->val; + rtld_recalc_bind_not(ld_get_env_var(LD_BIND_NOT)); +} + +static void +rtld_set_var_bind_not(struct ld_env_var_desc *lvd) +{ + rtld_recalc_bind_not(lvd->val); +} + +static void +rtld_set_var_dynamic_weak(struct ld_env_var_desc *lvd) +{ + ld_dynamic_weak = lvd->val == NULL; +} + +static void +rtld_set_var_loadfltr(struct ld_env_var_desc *lvd) +{ + ld_loadfltr = lvd->val != NULL; +} + +static void +rtld_set_var_libmap_disable(struct ld_env_var_desc *lvd) +{ + libmap_disable = lvd->val != NULL; +} + int rtld_set_var(const char *name, const char *val) { + RtldLockState lockstate; struct ld_env_var_desc *lvd; u_int i; + int error; + error = ENOENT; + wlock_acquire(rtld_bind_lock, &lockstate); for (i = 0; i < nitems(ld_env_vars); i++) { lvd = &ld_env_vars[i]; if (strcmp(lvd->n, name) != 0) continue; - if (!lvd->can_update || (lvd->unsecure && !trust)) - return (EPERM); + if (!lvd->can_update || (lvd->unsecure && !trust)) { + error = EPERM; + break; + } if (lvd->owned) free(__DECONST(char *, lvd->val)); if (val != NULL) @@ -6630,11 +6802,15 @@ rtld_set_var(const char *name, const char *val) else lvd->val = NULL; lvd->owned = true; - if (lvd->debug) - debug = lvd->val != NULL && *lvd->val != '\0'; - return (0); + if (lvd->on_update != NULL) + lvd->on_update(lvd); + error = 0; + break; } - return (ENOENT); + if (error == 0) + rtld_recalc_dangerous_ld_env(); + lock_release(rtld_bind_lock, &lockstate); + return (error); } /* diff --git a/libexec/rtld-elf/tests/Makefile b/libexec/rtld-elf/tests/Makefile index c4b3baab4cb8..4fbc32d87615 100644 --- a/libexec/rtld-elf/tests/Makefile +++ b/libexec/rtld-elf/tests/Makefile @@ -7,12 +7,14 @@ SUBDIR_DEPEND_target= libpythagoras ATF_TESTS_C= ld_library_pathfds ATF_TESTS_C+= ld_preload_fds +ATF_TESTS_C+= set_var_test .for t in ${ATF_TESTS_C} SRCS.$t= $t.c common.c .endfor ATF_TESTS_C+= dlopen_test +ATF_TESTS_C+= dlopen_hash_test WARNS?= 3 diff --git a/libexec/rtld-elf/tests/dlopen_hash_test.c b/libexec/rtld-elf/tests/dlopen_hash_test.c new file mode 100644 index 000000000000..a95ebdb34381 --- /dev/null +++ b/libexec/rtld-elf/tests/dlopen_hash_test.c @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alex S <iwtcex@gmail.com> + * Copyright 2026 The FreeBSD Foundation + * + * Portions of this software were developed by + * Konstantin Belousov <kib@FreeBSD.org> under sponsorship from + * the FreeBSD Foundation. + */ + +#include <atf-c.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <link.h> +#include <stdio.h> + +ATF_TC_WITHOUT_HEAD(dlopen_hash); +ATF_TC_BODY(dlopen_hash, tc) +{ + void *handle; + char *pathfds; + char *name; + int testdir; + + handle = dlopen("libpythagoras.so.0", RTLD_LAZY); + ATF_REQUIRE(handle == NULL); + + testdir = open(atf_tc_get_config_var(tc, "srcdir"), + O_RDONLY | O_DIRECTORY); + ATF_REQUIRE(testdir >= 0); + + ATF_REQUIRE(asprintf(&pathfds, "%d", testdir) > 0); + ATF_REQUIRE(rtld_set_var("LIBRARY_PATH_FDS", pathfds) == 0); + + ATF_REQUIRE(asprintf(&name, "#%d/libpythagoras.so.0", testdir) > 0); + handle = dlopen(name, RTLD_LAZY); + ATF_REQUIRE(handle != NULL); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dlopen_hash); + return atf_no_error(); +} diff --git a/libexec/rtld-elf/tests/set_var_test.c b/libexec/rtld-elf/tests/set_var_test.c new file mode 100644 index 000000000000..6279bd5ecb44 --- /dev/null +++ b/libexec/rtld-elf/tests/set_var_test.c @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 Alex S <iwtcex@gmail.com> + */ + +#include <atf-c.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <link.h> +#include <stdio.h> + +ATF_TC_WITHOUT_HEAD(set_var_library_path_fds); +ATF_TC_BODY(set_var_library_path_fds, tc) +{ + void *handle; + char *pathfds; + int testdir; + + handle = dlopen("libpythagoras.so.0", RTLD_LAZY); + ATF_REQUIRE(handle == NULL); + + testdir = open(atf_tc_get_config_var(tc, "srcdir"), + O_RDONLY | O_DIRECTORY); + ATF_REQUIRE(testdir >= 0); + + ATF_REQUIRE(asprintf(&pathfds, "%d", testdir) > 0); + ATF_REQUIRE(rtld_set_var("LIBRARY_PATH_FDS", pathfds) == 0); + + handle = dlopen("libpythagoras.so.0", RTLD_LAZY); + ATF_REQUIRE(handle != NULL); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, set_var_library_path_fds); + return atf_no_error(); +} |
