diff --git a/meta/conf/distro/include/maintainers.inc b/meta/conf/distro/include/maintainers.inc
index f757fafdcb..86bf4d14ee 100644
--- a/meta/conf/distro/include/maintainers.inc
+++ b/meta/conf/distro/include/maintainers.inc
@@ -831,6 +831,7 @@ RECIPE_MAINTAINER:pn-systemd-boot-native = "Viswanath Kraleti <quic_vkraleti@qui
 RECIPE_MAINTAINER:pn-systemd-bootchart = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-bootconf = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-conf = "Chen Qi <Qi.Chen@windriver.com>"
+RECIPE_MAINTAINER:pn-systemd-hwdb-native = "Unassigned <unassigned@yoctoproject.org>"
 RECIPE_MAINTAINER:pn-systemd-machine-units = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-serialgetty = "Chen Qi <Qi.Chen@windriver.com>"
 RECIPE_MAINTAINER:pn-systemd-systemctl-native = "Chen Qi <Qi.Chen@windriver.com>"
diff --git a/meta/recipes-core/systemd/systemd-hwdb-native_261.bb b/meta/recipes-core/systemd/systemd-hwdb-native_261.bb
new file mode 100644
index 0000000000..0b3ff2ba03
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd-hwdb-native_261.bb
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: MIT
+FILESEXTRAPATHS:prepend := "${THISDIR}/systemd:"
+
+SUMMARY = "Hardware database management tool from systemd"
+
+require systemd.inc
+
+DEPENDS = "gperf-native libcap-native util-linux-native python3-jinja2-native"
+
+# TODO: Remove STATX_MNT_ID patch once minimum supported build host kernel is >= 5.8 (RHEL 8 EOL: 2029)
+SRC_URI += "file://Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch \
+            file://hwdb-use-compat-mode-for-reproducible-cross-builds.patch \
+           "
+
+inherit pkgconfig meson native
+
+MESON_TARGET = "systemd-hwdb:executable"
+
+# Override prefix so compiled-in UDEVLIBEXECDIR (/usr/lib/udev) matches the
+# target rootfs layout. This allows --root $D --usr to find hwdb.d source
+# files and write hwdb.bin to the correct location.
+EXTRA_OEMESON += "--prefix /usr"
+EXTRA_OEMESON += "-Dhwdb=true -Dlink-udev-shared=false"
+EXTRA_OEMESON += "-Dpam=disabled -Daudit=disabled -Dselinux=disabled"
+EXTRA_OEMESON += "-Dacl=disabled -Dapparmor=disabled -Dseccomp=disabled"
+EXTRA_OEMESON += "-Dlibcryptsetup=disabled -Dlibcurl=disabled -Dlibfido2=disabled"
+EXTRA_OEMESON += "-Dpcre2=disabled -Dp11kit=disabled -Dopenssl=disabled"
+
+do_install() {
+    install -d ${D}${bindir}
+    install -m 0755 ${B}/systemd-hwdb ${D}${bindir}/systemd-hwdb
+}
diff --git a/meta/recipes-core/systemd/systemd-systemctl-native_261.bb b/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
index 686448cf1e..a6ad6901e0 100644
--- a/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
+++ b/meta/recipes-core/systemd/systemd-systemctl-native_261.bb
@@ -6,6 +6,9 @@ require systemd.inc
 
 DEPENDS = "gperf-native libcap-native util-linux-native python3-jinja2-native"
 
+# TODO: Remove STATX_MNT_ID patch once minimum supported build host kernel is >= 5.8 (RHEL 8 EOL: 2029)
+SRC_URI += "file://Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch"
+
 inherit pkgconfig meson native
 
 MESON_TARGET = "systemctl:executable"
diff --git a/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch b/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch
new file mode 100644
index 0000000000..c63423cc96
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/Handle-missing-pidfd_open-and-STATX_MNT_ID-on-older-.patch
@@ -0,0 +1,176 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Turull <daniel.turull@ericsson.com>
+Date: Mon, 23 Jun 2026 12:00:00 +0200
+Subject: [PATCH] Handle missing pidfd_open and STATX_MNT_ID on older kernels
+
+On hosts lacking pidfd_open (kernel < 5.3) or STATX_MNT_ID (kernel < 5.8,
+e.g. RHEL 8), native tools (systemctl --root, systemd-hwdb --root) fail
+during path resolution. Fix by:
+
+- Treating ENOSYS/EOPNOTSUPP from pidfd_open as graceful fallback.
+- Adding fd_get_mount_id() to read mnt_id from /proc/self/fdinfo (available
+  since kernel 3.15) and using it as fallback when statx returns -EUNATCH in
+  fds_inode_and_mount_same() and chase_statx().
+
+This restores the /proc/self/fdinfo fallback that existed in systemd 259
+(fd_fdinfo_mnt_id in mountpoint-util.c) but was removed upstream in 260+.
+
+This patch is only applied to native recipes (systemd-systemctl-native,
+systemd-hwdb-native) where /proc/self/fdinfo is guaranteed available.
+Do NOT apply to the target systemd recipe.
+
+Upstream-Status: Inappropriate [oe specific]
+
+Assisted-by: kiro:claude-opus-4.6
+Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
+---
+ src/basic/chase.c   | 20 ++++++++++++++-
+ src/basic/fd-util.c | 63 +++++++++++++++++++++++++++++++++++++++++++--
+ src/basic/fd-util.h |  1 +
+ src/basic/pidref.c  |  4 +--
+ 4 files changed, 83 insertions(+), 5 deletions(-)
+
+--- a/src/basic/pidref.c	2026-06-25 14:01:12.007875484 +0200
++++ b/src/basic/pidref.c	2026-06-25 14:01:55.098770206 +0200
+@@ -106,8 +106,8 @@ int pidref_set_pid(PidRef *pidref, pid_t
+ 
+         fd = pidfd_open(pid, 0);
+         if (fd < 0) {
+-                /* Graceful fallback in case the kernel is out of fds */
+-                if (!ERRNO_IS_RESOURCE(errno))
++                /* Graceful fallback in case the kernel is out of fds or lacks pidfd support */
++                if (!ERRNO_IS_RESOURCE(errno) && !ERRNO_IS_NOT_SUPPORTED(errno))
+                         return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid);
+ 
+                 fd = -EBADF;
+--- a/src/basic/fd-util.h	2026-06-25 14:01:12.009875526 +0200
++++ b/src/basic/fd-util.h	2026-06-25 14:01:20.909060415 +0200
+@@ -188,6 +188,7 @@ static inline int dir_fd_is_root_or_cwd(
+ }
+ 
+ int fds_inode_and_mount_same(int fd1, int fd2);
++int fd_get_mount_id(int fd, uint64_t *ret);
+ 
+ int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer);
+ 
+--- a/src/basic/fd-util.c	2026-06-25 14:01:12.011875567 +0200
++++ b/src/basic/fd-util.c	2026-06-25 14:01:40.007456905 +0200
+@@ -1082,6 +1082,38 @@ int path_is_root_at(int dir_fd, const ch
+         return fds_inode_and_mount_same(dir_fd, XAT_FDROOT);
+ }
+ 
++int fd_get_mount_id(int fd, uint64_t *ret) {
++        char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
++        _cleanup_close_ int real_fd = -EBADF;
++        _cleanup_free_ char *p = NULL;
++        uint64_t mnt_id;
++        int r;
++
++        assert(ret);
++
++        /* /proc/self/fdinfo/ requires a real fd; resolve AT_FDCWD/XAT_FDROOT via O_PATH. */
++        if (fd == AT_FDCWD || fd == XAT_FDROOT) {
++                real_fd = open(fd == XAT_FDROOT ? "/" : ".", O_PATH|O_CLOEXEC);
++                if (real_fd < 0)
++                        return -errno;
++                fd = real_fd;
++        }
++
++        assert(fd >= 0);
++        xsprintf(path, "/proc/self/fdinfo/%i", fd);
++
++        r = get_proc_field(path, "mnt_id", &p);
++        if (r < 0)
++                return r;
++
++        r = safe_atou64(p, &mnt_id);
++        if (r < 0)
++                return r;
++
++        *ret = mnt_id;
++        return 0;
++}
++
+ int fds_inode_and_mount_same(int fd1, int fd2) {
+         struct statx sx1, sx2;
+         int r;
+@@ -1092,7 +1124,20 @@ int fds_inode_and_mount_same(int fd1, in
+         r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH,
+                    STATX_TYPE|STATX_INO|STATX_MNT_ID,
+                    &sx1);
+-        if (r < 0)
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                /* Kernel lacks STATX_MNT_ID; fall back to /proc/self/fdinfo. */
++                r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH,
++                           STATX_TYPE|STATX_INO, &sx1);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd1, &mnt_id);
++                if (r < 0)
++                        return r;
++                sx1.stx_mnt_id = mnt_id;
++                sx1.stx_mask |= STATX_MNT_ID;
++        } else if (r < 0)
+                 return r;
+ 
+         if (fd1 == fd2) /* Shortcut things if fds are the same (only after validating the fd) */
+@@ -1101,7 +1146,19 @@ int fds_inode_and_mount_same(int fd1, in
+         r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH,
+                    STATX_TYPE|STATX_INO|STATX_MNT_ID,
+                    &sx2);
+-        if (r < 0)
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH,
++                           STATX_TYPE|STATX_INO, &sx2);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd2, &mnt_id);
++                if (r < 0)
++                        return r;
++                sx2.stx_mnt_id = mnt_id;
++                sx2.stx_mask |= STATX_MNT_ID;
++        } else if (r < 0)
+                 return r;
+ 
+         r = statx_mount_same(&sx1, &sx2);
+--- a/src/basic/chase.c	2026-06-25 14:01:12.013875609 +0200
++++ b/src/basic/chase.c	2026-06-25 14:01:47.117604514 +0200
+@@ -40,7 +40,9 @@
+         (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET)
+ 
+ static int chase_statx(int fd, struct statx *ret) {
+-        return xstatx_full(fd,
++        int r;
++
++        r = xstatx_full(fd,
+                         /* path= */ NULL,
+                         /* statx_flags= */ 0,
+                         XSTATX_MNT_ID_BEST,
+@@ -48,6 +50,23 @@ static int chase_statx(int fd, struct st
+                         /* optional_mask= */ 0,
+                         /* mandatory_attributes= */ 0,
+                         ret);
++        if (r == -EUNATCH) {
++                uint64_t mnt_id;
++
++                /* Kernel lacks STATX_MNT_ID; fall back to /proc/self/fdinfo. */
++                r = xstatx(fd, /* path= */ NULL, /* statx_flags= */ 0,
++                           STATX_TYPE|STATX_UID|STATX_INO,
++                           ret);
++                if (r < 0)
++                        return r;
++                r = fd_get_mount_id(fd, &mnt_id);
++                if (r < 0)
++                        return r;
++                ret->stx_mnt_id = mnt_id;
++                ret->stx_mask |= STATX_MNT_ID;
++        }
++
++        return r;
+ }
+ 
+ static int chase_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) {
diff --git a/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch b/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch
new file mode 100644
index 0000000000..bb90105cbd
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/hwdb-use-compat-mode-for-reproducible-cross-builds.patch
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Turull <daniel.turull@ericsson.com>
+Date: Wed, 25 Jun 2026 10:00:00 +0200
+Subject: [PATCH] hwdb: use compat mode to avoid embedding source paths
+
+Use compat=true in systemd-hwdb's verb_update() so that source
+filenames, line numbers, and priorities are not embedded in hwdb.bin.
+
+Without this, when --root $D is used during cross-compilation, the
+absolute build paths (e.g. /tmp/work/.../rootfs/usr/lib/udev/hwdb.d/...)
+are written into the database, causing:
+- Non-reproducible builds (different TMPDIR → different hwdb.bin)
+- Build directory path leakage into the target image
+
+The compat format matches what udevadm hwdb (the deprecated path)
+has always produced, and is the expected format for cross-built images.
+
+Upstream-Status: Inappropriate [oe specific]
+
+AI-Generated: Claude Opus 4.6
+Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
+---
+ src/hwdb/hwdb.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/hwdb/hwdb.c
++++ b/src/hwdb/hwdb.c
+@@ -27,7 +27,7 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata)
+         if (hwdb_bypass())
+                 return 0;
+ 
+-        return hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, false);
++        return hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, true);
+ }
+ 
+ static int help(void) {
diff --git a/meta/recipes-core/systemd/systemd_261.bb b/meta/recipes-core/systemd/systemd_261.bb
index eedce348c3..22bd4ca5fc 100644
--- a/meta/recipes-core/systemd/systemd_261.bb
+++ b/meta/recipes-core/systemd/systemd_261.bb
@@ -910,7 +910,7 @@ pkg_prerm:${PN}:libc-glibc () {
 	fi
 }
 
-PACKAGE_WRITE_DEPS += "qemuwrapper-cross"
+PACKAGE_WRITE_DEPS += "qemuwrapper-cross systemd-hwdb-native"
 
 pkg_postinst:udev-hwdb () {
 	if test -n "$D"; then
diff --git a/scripts/postinst-intercepts/update_udev_hwdb b/scripts/postinst-intercepts/update_udev_hwdb
index 8b3f5de791..d7a4ffc294 100644
--- a/scripts/postinst-intercepts/update_udev_hwdb
+++ b/scripts/postinst-intercepts/update_udev_hwdb
@@ -19,7 +19,23 @@ case "${PREFERRED_PROVIDER_udev}" in
 		;;
 esac
 
-rm -f $D${UDEVLIBDIR}/udev/hwdb.bin
-PSEUDO_UNLOAD=1 ${binprefix}qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS} ||
-	PSEUDO_UNLOAD=1 qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS}
-chown root:root $D${UDEVLIBDIR}/udev/hwdb.bin
+hwdb_bin="$D${UDEVLIBDIR}/udev/hwdb.bin"
+rm -f "$hwdb_bin"
+
+# Use native systemd-hwdb to generate hwdb.bin at build time.
+# This avoids QEMU user-mode emulation and works on host kernels < 5.8
+# (e.g. RHEL 8) where systemd 261+ would fail due to missing STATX_MNT_ID.
+NATIVE_HWDB="${STAGING_DIR_NATIVE}/usr/bin/systemd-hwdb"
+if test -x "$NATIVE_HWDB" && test "${PREFERRED_PROVIDER_udev}" = "systemd"; then
+	PSEUDO_UNLOAD=1 $NATIVE_HWDB update --root $D ${UDEV_EXTRA_ARGS}
+else
+	PSEUDO_UNLOAD=1 ${binprefix}qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS} ||
+		PSEUDO_UNLOAD=1 qemuwrapper -L $D $D${UDEVADM} hwdb --update --root $D ${UDEV_EXTRA_ARGS}
+fi
+
+if ! test -s "$hwdb_bin"; then
+	echo "ERROR: hwdb.bin was not created at $hwdb_bin" >&2
+	echo "The hwdb generation command exited successfully but produced no output." >&2
+	exit 1
+fi
+chown root:root "$hwdb_bin"
