Page MenuHomeFreeBSD

D58855.id.diff
No OneTemporary

D58855.id.diff

diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c
--- a/sys/kern/vfs_lookup.c
+++ b/sys/kern/vfs_lookup.c
@@ -613,11 +613,17 @@
struct componentname *cnp;
struct thread *td;
struct pwd *pwd;
+ char *abi_symlink; /* abs symlink expansion from the ABI pass */
+ size_t abi_symlink_len;
+ bool abi_symlink_pass; /* this pass walks abi_symlink natively */
int error;
enum cache_fpl_status status;
cnp = &ndp->ni_cnd;
td = curthread;
+ abi_symlink = NULL;
+ abi_symlink_len = 0;
+ abi_symlink_pass = false;
#ifdef INVARIANTS
KASSERT((ndp->ni_debugflags & NAMEI_DBG_CALLED) == 0,
("%s: repeated call to namei without NDREINIT", __func__));
@@ -655,7 +661,18 @@
ndp->ni_loopcnt = 0;
ndp->ni_vp = NULL;
- error = namei_getpath(ndp);
+ if (__predict_false(abi_symlink_pass && abi_symlink != NULL)) {
+ /*
+ * Retry the saved absolute symlink expansion from the
+ * native root instead of reloading the original path;
+ * see the ENOENT handling after vfs_lookup() below.
+ */
+ cnp->cn_pnbuf = abi_symlink;
+ ndp->ni_pathlen = abi_symlink_len;
+ abi_symlink = NULL;
+ error = 0;
+ } else
+ error = namei_getpath(ndp);
if (__predict_false(error != 0)) {
namei_cleanup_cnp(cnp);
nameicap_cleanup(ndp, error);
@@ -738,13 +755,26 @@
error = vfs_lookup(ndp);
if (error != 0) {
uint64_t was_restarted;
- bool abi_restart;
+ bool abi_restart, symlink_retry;
was_restarted = ndp->ni_cnd.cn_flags &
ISRESTARTED;
abi_restart = pwd->pwd_adir != pwd->pwd_rdir &&
error == ENOENT && was_restarted == 0;
- if (error != ERESTART && !abi_restart)
+ /*
+ * If the walk of an absolute symlink expansion
+ * failed under the ABI root, retry the expansion
+ * from the native root before falling back to the
+ * original path: the target may name the file
+ * only in the native namespace (host-integration
+ * symlinks under the ABI root, linprocfs'
+ * /proc/<pid>/exe). If the native walk of the
+ * expansion also fails, fall back to the original
+ * path as before.
+ */
+ symlink_retry = abi_symlink_pass && error == ENOENT;
+ if (error != ERESTART && !abi_restart &&
+ !symlink_retry)
goto out;
nameicap_cleanup(ndp, error);
pwd_drop(pwd);
@@ -753,6 +783,12 @@
if (abi_restart)
was_restarted = ISRESTARTED;
ndp->ni_cnd.cn_flags |= was_restarted;
+ abi_symlink_pass = abi_restart &&
+ abi_symlink != NULL;
+ if (!abi_symlink_pass && abi_symlink != NULL) {
+ uma_zfree(namei_zone, abi_symlink);
+ abi_symlink = NULL;
+ }
goto restart;
}
@@ -764,6 +800,8 @@
ndp->ni_vp, false, ndp);
nameicap_cleanup(ndp, 0);
pwd_drop(pwd);
+ if (abi_symlink != NULL)
+ uma_zfree(namei_zone, abi_symlink);
NDVALIDATE(ndp);
return (0);
}
@@ -789,6 +827,22 @@
*/
if ((cnp->cn_flags & ISRESTARTED) != 0)
ndp->ni_rootdir = pwd->pwd_rdir;
+ else if (pwd->pwd_adir != pwd->pwd_rdir) {
+ /*
+ * Save the expansion so that, should the
+ * walk of the target under the ABI root
+ * fail, the ENOENT handling above can retry
+ * it from the native root, giving symlink
+ * targets the same ABI-first native-fallback
+ * treatment original paths receive.
+ */
+ if (abi_symlink == NULL)
+ abi_symlink = uma_zalloc(namei_zone,
+ M_WAITOK);
+ memcpy(abi_symlink, cnp->cn_pnbuf,
+ ndp->ni_pathlen);
+ abi_symlink_len = ndp->ni_pathlen;
+ }
vrele(dp);
error = namei_handle_root(ndp, &dp);
if (error != 0)
@@ -804,6 +858,8 @@
namei_cleanup_cnp(cnp);
nameicap_cleanup(ndp, error);
pwd_drop(pwd);
+ if (abi_symlink != NULL)
+ uma_zfree(namei_zone, abi_symlink);
return (error);
}
diff --git a/tests/sys/vfs/Makefile b/tests/sys/vfs/Makefile
--- a/tests/sys/vfs/Makefile
+++ b/tests/sys/vfs/Makefile
@@ -5,6 +5,8 @@
ATF_TESTS_C+= lookup_cap_dotdot
CFLAGS.lookup_cap_dotdot.c+= -I${SRCTOP}/tests
+ATF_TESTS_SH+= abi_root_symlink
+
#ATF_TESTS_SH+= lookup_test
TAP_TESTS_SH+= trailing_slash
diff --git a/tests/sys/vfs/abi_root_symlink.sh b/tests/sys/vfs/abi_root_symlink.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/vfs/abi_root_symlink.sh
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org>
+#
+# Regression tests for absolute symlink resolution under an ABI root
+# (compat.linux.emul_path). namei(9) resolves paths for Linux ABI
+# processes by trying the ABI root first and falling back to the
+# native root. Absolute symlink targets receive the same treatment:
+# a target is first resolved under the ABI root (so self-contained
+# Linux userlands keep working, PR 289739) and retried from the
+# native root when that fails (so symlinks pointing at host paths,
+# and linprocfs' /proc/<pid>/exe, keep working, PR 297426).
+#
+# These tests require the Linux ABI (linux_enable=YES) and a Linux
+# userland (e.g. emulators/linux_base-rl9) providing ${emul_path}/bin/sh.
+
+require_linux()
+{
+ emul=$(sysctl -n compat.linux.emul_path 2>/dev/null)
+ [ "$emul" ] || atf_skip "Linux ABI not present (compat.linux.emul_path)"
+ kldstat -q -m linux64 || kldstat -q -m linux ||
+ atf_skip "linux(4) not loaded"
+ lsh="$emul/bin/sh"
+ [ -x "$lsh" ] || atf_skip "Linux userland not installed ($lsh)"
+ # Mirror of the ATF work directory under the ABI root.
+ wd=$(pwd)
+ abiwd="$emul$wd"
+ atf_check mkdir -p "$abiwd"
+}
+
+cleanup_linux()
+{
+ emul=$(sysctl -n compat.linux.emul_path 2>/dev/null)
+ [ "$emul" ] || return 0
+ rm -rf "$emul$(pwd)" 2>/dev/null
+ # Remove now-empty parents mirrored under the ABI root.
+ rmdir -p "$emul$(dirname "$(pwd)")" 2>/dev/null
+ return 0
+}
+
+atf_test_case symlink_target_in_abi_root cleanup
+symlink_target_in_abi_root_head()
+{
+ atf_set "descr" "Absolute symlink target under the ABI root wins" \
+ "over an identically named native file (PR 289739)"
+ atf_set "require.user" "root"
+}
+symlink_target_in_abi_root_body()
+{
+ require_linux
+ printf native > "$wd/f-both"
+ printf abi > "$abiwd/f-both"
+ atf_check ln -s "$wd/f-both" "$abiwd/link-both"
+ atf_check -o inline:"abi" "$lsh" -c "cat $wd/link-both"
+}
+symlink_target_in_abi_root_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case symlink_target_native_only cleanup
+symlink_target_native_only_head()
+{
+ atf_set "descr" "Absolute symlink under the ABI root pointing at" \
+ "a native-only file falls back to the native root (PR 297426)"
+ atf_set "require.user" "root"
+}
+symlink_target_native_only_body()
+{
+ require_linux
+ printf native > "$wd/f-native"
+ atf_check ln -s "$wd/f-native" "$abiwd/link-native"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/link-native"
+}
+symlink_target_native_only_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case symlink_target_native_only_midpath cleanup
+symlink_target_native_only_midpath_head()
+{
+ atf_set "descr" "Absolute directory symlink under the ABI root" \
+ "pointing at a native-only directory falls back to the native" \
+ "root for lookups through it (PR 297426); the shape of" \
+ "linux-rl9-fontconfig's /etc/fonts -> /usr/local/etc/fonts"
+ atf_set "require.user" "root"
+}
+symlink_target_native_only_midpath_body()
+{
+ require_linux
+ atf_check mkdir "$wd/ndir"
+ printf native > "$wd/ndir/f"
+ atf_check ln -s "$wd/ndir" "$abiwd/dirlink"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/dirlink/f"
+}
+symlink_target_native_only_midpath_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case plain_native_fallback cleanup
+plain_native_fallback_head()
+{
+ atf_set "descr" "Plain path (no symlink) absent under the ABI root" \
+ "still falls back to the native root"
+ atf_set "require.user" "root"
+}
+plain_native_fallback_body()
+{
+ require_linux
+ printf native > "$wd/f-plain"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/f-plain"
+}
+plain_native_fallback_cleanup()
+{
+ cleanup_linux
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case symlink_target_in_abi_root
+ atf_add_test_case symlink_target_native_only
+ atf_add_test_case symlink_target_native_only_midpath
+ atf_add_test_case plain_native_fallback
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Aug 24, 8:53 AM (1 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37179174
Default Alt Text
D58855.id.diff (7 KB)

Event Timeline