new file mode 100644
@@ -0,0 +1,124 @@
+From 490d550204f22765d04dd012f51f1bf74b8c4543 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Erik=20Sj=C3=B6lund?= <erik.sjolund@gmail.com>
+Date: Wed, 29 Jan 2025 18:42:02 +0100
+Subject: [PATCH] linux, utils: remove dead code crun_ensure_file*()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Remove dead code.
+
+Use 0620 instead of 0700 as mode
+for the new file.
+
+Signed-off-by: Erik Sjölund <erik.sjolund@gmail.com>
+
+CVE: CVE-2026-88264
+Upstream-Status: Backport [https://github.com/containers/crun/commit/490d550204f22765d04dd012f51f1bf74b8c4543]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ src/libcrun/linux.c | 2 +-
+ src/libcrun/utils.c | 37 +++++--------------------------------
+ src/libcrun/utils.h | 6 +-----
+ 3 files changed, 7 insertions(+), 38 deletions(-)
+
+diff --git a/src/libcrun/linux.c b/src/libcrun/linux.c
+index acfb5b0b..6ecbaaac 100644
+--- a/src/libcrun/linux.c
++++ b/src/libcrun/linux.c
+@@ -1836,7 +1836,7 @@ create_missing_devs (libcrun_container_t *container, bool binds, libcrun_error_t
+
+ if (container->container_def->process && container->container_def->process->terminal)
+ {
+- ret = crun_ensure_file_at (devfd, "console", 0620, true, err);
++ ret = create_file_if_missing_at (devfd, "console", 0620, err);
+ if (UNLIKELY (ret < 0))
+ return ret;
+ }
+diff --git a/src/libcrun/utils.c b/src/libcrun/utils.c
+index fd40110a..38d86c82 100644
+--- a/src/libcrun/utils.c
++++ b/src/libcrun/utils.c
+@@ -242,17 +242,17 @@ get_file_type (mode_t *mode, bool nofollow, const char *path)
+ }
+
+ int
+-create_file_if_missing_at (int dirfd, const char *file, libcrun_error_t *err)
++create_file_if_missing_at (int dirfd, const char *file, mode_t mode, libcrun_error_t *err)
+ {
+- cleanup_close int fd_write = openat (dirfd, file, O_CLOEXEC | O_CREAT | O_WRONLY, 0700);
++ cleanup_close int fd_write = openat (dirfd, file, O_CLOEXEC | O_CREAT | O_WRONLY, mode);
+ if (fd_write < 0)
+ {
+- mode_t mode;
++ mode_t tmp_mode;
+ int ret;
+
+ /* On errors, check if the file already exists. */
+- ret = get_file_type_at (dirfd, &mode, false, file);
+- if (ret == 0 && S_ISREG (mode))
++ ret = get_file_type_at (dirfd, &tmp_mode, false, file);
++ if (ret == 0 && S_ISREG (tmp_mode))
+ return 0;
+
+ return crun_make_error (err, errno, "creating file `%s`", file);
+@@ -635,33 +635,6 @@ crun_ensure_directory (const char *path, int mode, bool nofollow, libcrun_error_
+ return crun_ensure_directory_at (AT_FDCWD, path, mode, nofollow, err);
+ }
+
+-int
+-crun_ensure_file_at (int dirfd, const char *path, int mode, bool nofollow, libcrun_error_t *err)
+-{
+- cleanup_free char *tmp = xstrdup (path);
+- size_t len = strlen (tmp);
+- char *it = tmp + len - 1;
+- int ret;
+-
+- while (*it != '/' && it > tmp)
+- it--;
+- if (it > tmp)
+- {
+- *it = '\0';
+- ret = crun_ensure_directory_at (dirfd, tmp, mode, nofollow, err);
+- if (UNLIKELY (ret < 0))
+- return ret;
+- *it = '/';
+- }
+- return create_file_if_missing_at (dirfd, tmp, err);
+-}
+-
+-int
+-crun_ensure_file (const char *path, int mode, bool nofollow, libcrun_error_t *err)
+-{
+- return crun_ensure_file_at (AT_FDCWD, path, mode, nofollow, err);
+-}
+-
+ static int
+ get_file_size (int fd, off_t *size)
+ {
+diff --git a/src/libcrun/utils.h b/src/libcrun/utils.h
+index ed83344e..0025648f 100644
+--- a/src/libcrun/utils.h
++++ b/src/libcrun/utils.h
+@@ -265,12 +265,8 @@ int write_file_at_with_flags (int dirfd, int flags, mode_t mode, const char *nam
+
+ int crun_ensure_directory (const char *path, int mode, bool nofollow, libcrun_error_t *err);
+
+-int crun_ensure_file (const char *path, int mode, bool nofollow, libcrun_error_t *err);
+-
+ int crun_ensure_directory_at (int dirfd, const char *path, int mode, bool nofollow, libcrun_error_t *err);
+
+-int crun_ensure_file_at (int dirfd, const char *path, int mode, bool nofollow, libcrun_error_t *err);
+-
+ int crun_safe_create_and_open_ref_at (bool dir, int dirfd, const char *dirpath, size_t dirpath_len, const char *path, int mode, libcrun_error_t *err);
+
+ int crun_safe_ensure_directory_at (int dirfd, const char *dirpath, size_t dirpath_len, const char *path, int mode,
+@@ -285,7 +281,7 @@ int crun_dir_p_at (int dirfd, const char *path, bool nofollow, libcrun_error_t *
+
+ int detach_process ();
+
+-int create_file_if_missing_at (int dirfd, const char *file, libcrun_error_t *err);
++int create_file_if_missing_at (int dirfd, const char *file, mode_t mode, libcrun_error_t *err);
+
+ int check_running_in_user_namespace (libcrun_error_t *err);
+
new file mode 100644
@@ -0,0 +1,104 @@
+From ef32479522af883568ce4a5482358069ff71dc8f Mon Sep 17 00:00:00 2001
+From: Giuseppe Scrivano <gscrivan@redhat.com>
+Date: Fri, 11 Sep 2026 09:25:48 +0000
+Subject: [PATCH] utils: do not follow symlinks when creating /dev/console
+
+create_file_if_missing_at() is only used to create `/dev/console` in the
+container rootfs when a terminal is requested, and it opened the file
+with O_CREAT but without O_NOFOLLOW. A rootfs providing `/dev/console`
+as a symlink made the open follow it, and since the devices are created
+before the pivot_root the target was resolved against the host file
+system, so crun created a root owned file at a path chosen by the
+container image.
+
+Open the file with O_NOFOLLOW, and check for an already existing file
+without following symlinks either, so that a symlink is not mistaken for
+a regular file that is already there.
+
+This is only reachable when nothing is mounted over /dev, so the rootfs
+entry stays visible; the configurations generated by Docker, Podman,
+containerd, CRI-O and `crun spec` mount a tmpfs there.
+
+Add a test case.
+
+Fixes: CVE-2026-88264
+Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
+
+CVE: CVE-2026-88264
+Upstream-Status: Backport [https://github.com/containers/crun/commit/ef32479522af883568ce4a5482358069ff71dc8f]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ src/libcrun/utils.c | 4 ++--
+ tests/test_devices.py | 36 ++++++++++++++++++++++++++++++++++++
+ 2 files changed, 38 insertions(+), 2 deletions(-)
+
+diff --git a/src/libcrun/utils.c b/src/libcrun/utils.c
+index ce44c261..76175411 100644
+--- a/src/libcrun/utils.c
++++ b/src/libcrun/utils.c
+@@ -244,14 +244,14 @@ get_file_type (mode_t *mode, bool nofollow, const char *path)
+ int
+ create_file_if_missing_at (int dirfd, const char *file, mode_t mode, libcrun_error_t *err)
+ {
+- cleanup_close int fd_write = openat (dirfd, file, O_CLOEXEC | O_CREAT | O_WRONLY, mode);
++ cleanup_close int fd_write = openat (dirfd, file, O_CLOEXEC | O_CREAT | O_WRONLY | O_NOFOLLOW, mode);
+ if (fd_write < 0)
+ {
+ mode_t tmp_mode;
+ int ret;
+
+ /* On errors, check if the file already exists. */
+- ret = get_file_type_at (dirfd, &tmp_mode, false, file);
++ ret = get_file_type_at (dirfd, &tmp_mode, true, file);
+ if (ret == 0 && S_ISREG (tmp_mode))
+ return 0;
+
+diff --git a/tests/test_devices.py b/tests/test_devices.py
+index 6956e2bd..a99753a3 100755
+--- a/tests/test_devices.py
++++ b/tests/test_devices.py
+@@ -214,8 +214,44 @@ def test_mknod_device():
+ return -1
+ return 0
+
++def test_dev_console_symlink_does_not_escape_rootfs():
++ escaped = os.path.join(get_tests_root(), "escaped-console")
++
++ conf = base_config()
++ add_all_namespaces(conf)
++ conf['process']['terminal'] = True
++ conf['process']['args'] = ['/init', 'true']
++ conf['mounts'] = [i for i in conf['mounts'] if not i['destination'].startswith("/dev")]
++ # a hook forces the deferred pivot_root, so the rootfs is still reached
++ # through the host file system when the devices are created.
++ conf['hooks'] = {"createRuntime": [{"path": "/bin/true"}]}
++
++ def prepare_rootfs(rootfs):
++ os.symlink(escaped, os.path.join(rootfs, "dev", "console"))
++
++ output = None
++ try:
++ run_and_get_output(conf, callback_prepare_rootfs=prepare_rootfs)
++ except Exception as e:
++ output = e.output.decode()
++
++ if os.path.lexists(escaped):
++ logger.error("`%s` was created outside the rootfs", escaped)
++ return -1
++
++ if output is None:
++ logger.error("the container was not refused")
++ return -1
++
++ if "create file `console`" not in output:
++ logger.error("the container failed for a different reason: %s", output)
++ return -1
++
++ return 0
++
+
+ all_tests = {
++ "dev-console-symlink-does-not-escape-rootfs": test_dev_console_symlink_does_not_escape_rootfs,
+ "owner-device" : test_owner_device,
+ "deny-devices" : test_deny_devices,
+ "allow-device" : test_allow_device,
@@ -16,6 +16,8 @@ SRC_URI = "git://github.com/containers/crun.git;branch=main;name=crun;protocol=h
git://github.com/opencontainers/image-spec.git;branch=main;name=ispec;destsuffix=git/libocispec/image-spec;protocol=https \
git://github.com/containers/yajl.git;branch=main;name=yajl;destsuffix=git/libocispec/yajl;protocol=https \
file://CVE-2025-24965.patch \
+ file://CVE-2026-88264-01.patch \
+ file://CVE-2026-88264-02.patch \
"
PV = "v1.14.3+git${SRCREV_crun}"