aboutsummaryrefslogtreecommitdiff
path: root/libexec
diff options
context:
space:
mode:
Diffstat (limited to 'libexec')
-rw-r--r--libexec/Makefile5
-rw-r--r--libexec/atf/atf-sh/Makefile1
-rwxr-xr-xlibexec/nuageinit/nuageinit2
-rw-r--r--libexec/nuageinit/tests/nuageinit.sh8
-rw-r--r--libexec/pkg-serve/Makefile9
-rw-r--r--libexec/pkg-serve/pkg-serve.8107
-rw-r--r--libexec/pkg-serve/pkg-serve.c180
-rw-r--r--libexec/pkg-serve/tests/Makefile5
-rw-r--r--libexec/pkg-serve/tests/pkg_serve_test.sh230
-rw-r--r--libexec/rc/rc.conf2
-rw-r--r--libexec/rc/rc.d/Makefile9
-rwxr-xr-xlibexec/rc/rc.d/NETWORKING2
-rw-r--r--libexec/rc/rc.d/virtual_oss6
-rw-r--r--libexec/rc/safe_eval.sh13
-rw-r--r--libexec/rtld-elf/aarch64/reloc.c2
-rw-r--r--libexec/rtld-elf/rtld.c254
-rw-r--r--libexec/rtld-elf/tests/Makefile2
-rw-r--r--libexec/rtld-elf/tests/dlopen_hash_test.c45
-rw-r--r--libexec/rtld-elf/tests/set_var_test.c38
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();
+}