Package: release.debian.org
Severity: normal
Tags: trixie
User: [email protected]
Usertags: pu
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:lxc

[ Reason ]
Fix two bugs affecting the version of lxc in trixie:

  * Cherry-pick upstream fix for data corruption during heavy IO on PTS
  * Update lxc-default-with-nesting apparmor profile

[ Impact ]
Users running lxc in trixie currently encounter small but annoying
bugs. The apparmor profile update in particular will resolve may issues
with running hardened systemd services within unprivilged containers.

[ Tests ]
The PTS data corruption patch has been heavily tested upstream, and
I've personally tested/verified the apparmor profile update.

[ Risks ]
Minor/none -- one targeted fix cherry-picked from the upstream git repo
and a simple apparmor profile update.

[ Checklist ]
  [*] *all* changes are documented in the d/changelog
  [*] I reviewed all changes and I approve them
  [*] attach debdiff against the package in (old)stable
  [*] the issue is verified as fixed in unstable

[ Changes ]
Two patches as outlined above.

[ Other info ]
The source debdiff is attached.
diff -Nru lxc-6.0.4/debian/changelog lxc-6.0.4/debian/changelog
--- lxc-6.0.4/debian/changelog	2025-12-26 19:02:22.000000000 +0000
+++ lxc-6.0.4/debian/changelog	2026-03-02 19:05:00.000000000 +0000
@@ -1,3 +1,10 @@
+lxc (1:6.0.4-4+deb13u2) trixie; urgency=medium
+
+  * Cherry-pick upstream fix for data corruption during heavy IO on PTS
+  * Update lxc-default-with-nesting apparmor profile (Closes: #1111087)
+
+ -- Mathias Gibbens <[email protected]>  Mon, 02 Mar 2026 19:05:00 +0000
+
 lxc (1:6.0.4-4+deb13u1) trixie; urgency=medium
 
   [ Frost ]
diff -Nru lxc-6.0.4/debian/patches/0001-nesting-Extend-mount-permissions-in-apparmor-to-allo.patch lxc-6.0.4/debian/patches/0001-nesting-Extend-mount-permissions-in-apparmor-to-allo.patch
--- lxc-6.0.4/debian/patches/0001-nesting-Extend-mount-permissions-in-apparmor-to-allo.patch	2025-12-26 19:02:22.000000000 +0000
+++ lxc-6.0.4/debian/patches/0001-nesting-Extend-mount-permissions-in-apparmor-to-allo.patch	2026-03-02 19:05:00.000000000 +0000
@@ -9,22 +9,26 @@
 It's only added in nesting profile as it could pose security risks on
 privileged containers.
 
+mount options=(rw,rbind) -> /run/systemd/mount-rootfs/,
+mount options=(rw,rbind) -> /run/systemd/mount-rootfs/**,
 mount options=(rw,rbind) -> /run/systemd/unit-root/,
 mount options=(rw,rbind) -> /run/systemd/unit-root/**,
 mount options=(rw,rshared) -> /,
 mount options=(rw,nosuid,nodev,noexec) proc -> /run/systemd/unit-root/proc/,
 ---
- config/apparmor/profiles/lxc-default-with-nesting | 4 ++++
- 1 file changed, 4 insertions(+)
+ config/apparmor/profiles/lxc-default-with-nesting | 6 ++++++
+ 1 file changed, 6 insertions(+)
 
 diff --git a/config/apparmor/profiles/lxc-default-with-nesting b/config/apparmor/profiles/lxc-default-with-nesting
-index cd198be..01562a9 100644
+index cd198be..55b17c8 100644
 --- a/config/apparmor/profiles/lxc-default-with-nesting
 +++ b/config/apparmor/profiles/lxc-default-with-nesting
-@@ -10,6 +10,10 @@ profile lxc-container-default-with-nesting flags=(attach_disconnected,mediate_de
+@@ -10,6 +10,12 @@ profile lxc-container-default-with-nesting flags=(attach_disconnected,mediate_de
    mount fstype=proc -> /var/cache/lxc/**,
    mount fstype=sysfs -> /var/cache/lxc/**,
    mount options=(rw,bind),
++  mount options=(rw,rbind) -> /run/systemd/mount-rootfs/,
++  mount options=(rw,rbind) -> /run/systemd/mount-rootfs/**,
 +  mount options=(rw,rbind) -> /run/systemd/unit-root/,
 +  mount options=(rw,rbind) -> /run/systemd/unit-root/**,
 +  mount options=(rw,rshared) -> /,
diff -Nru lxc-6.0.4/debian/patches/0105-cherry-pick-fix-heavy-io-pts.patch lxc-6.0.4/debian/patches/0105-cherry-pick-fix-heavy-io-pts.patch
--- lxc-6.0.4/debian/patches/0105-cherry-pick-fix-heavy-io-pts.patch	1970-01-01 00:00:00.000000000 +0000
+++ lxc-6.0.4/debian/patches/0105-cherry-pick-fix-heavy-io-pts.patch	2026-03-02 19:05:00.000000000 +0000
@@ -0,0 +1,335 @@
+From 4cb9884ed7d3ca98ccd9bf2abbd508255b4e1fb7 Mon Sep 17 00:00:00 2001
+From: DreamConnected <[email protected]>
+Date: Sun, 26 Oct 2025 13:28:13 +0800
+Subject: [PATCH 1/2] lxc/{terminal, file_utils}: ensure complete data writes
+ in ptx/peer io handlers
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Previously, lxc_write_nointr could return without writing all data
+when write() returned EAGAIN/EWOULDBLOCK due to buffer full conditions.
+
+This change:
+- Implements a loop to continue writing until all data is sent
+- Handles EINTR, EAGAIN, and EWOULDBLOCK errors appropriately
+- Uses poll() to wait for fd to become ready when blocked
+- Maintains backward compatibility while fixing partial write issues
+
+Signed-off-by: DreamConnected <[email protected]>
+[ alex ]
+- introduce a separate helper lxc_write_all and use it only in ptx/peer
+  io handlers
+- cleanup the code a bit
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+---
+ src/lxc/file_utils.c | 86 ++++++++++++++++++++++++++++++++++++++++++++
+ src/lxc/file_utils.h |  2 ++
+ src/lxc/terminal.c   |  4 +--
+ 3 files changed, 90 insertions(+), 2 deletions(-)
+
+diff --git a/src/lxc/file_utils.c b/src/lxc/file_utils.c
+index e8bf9321b0..ea54939f30 100644
+--- a/src/lxc/file_utils.c
++++ b/src/lxc/file_utils.c
+@@ -11,6 +11,7 @@
+ #include <sys/stat.h>
+ #include <sys/types.h>
+ #include <time.h>
++#include <poll.h>
+ 
+ #include "file_utils.h"
+ #include "macro.h"
+@@ -147,6 +148,91 @@ ssize_t lxc_read_try_buf_at(int dfd, const char *path, void *buf, size_t count)
+ 	return ret;
+ }
+ 
++static int __lxc_wait_for_io_ready(int fd, int event, int timeout_ms)
++{
++	int ret;
++	struct pollfd pfd = {
++		.fd = fd,
++		.events = event,
++		.revents = 0
++	};
++
++	do {
++		ret = poll(&pfd, 1, timeout_ms);
++	} while (ret < 0 && errno == EINTR);
++
++	if (ret < 0)
++		return -errno;
++
++	if (ret == 0)
++		return -ETIMEDOUT;
++
++	if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
++		if (pfd.revents & POLLERR)
++			return -EPIPE;
++		if (pfd.revents & POLLHUP)
++			return -EPIPE;
++		if (pfd.revents & POLLNVAL)
++			return -EBADF;
++	}
++
++	if (!(pfd.revents & event))
++		return -EAGAIN;
++
++	return ret;
++}
++
++ssize_t lxc_write_all(int fd, const void *buf, size_t count)
++{
++	ssize_t left_to_write = count;
++	const char *ptr = buf;
++	int flags;
++
++	flags = fcntl(fd, F_GETFL);
++	if (flags < 0)
++		return ret_set_errno(-1, errno);
++
++	/* only non-blocking fds are allowed */
++	if (!(flags & O_NONBLOCK))
++		return ret_set_errno(-1, EINVAL);
++
++	while (left_to_write > 0) {
++		int ret = write(fd, ptr, left_to_write);
++
++		if (ret > 0) {
++			left_to_write -= ret;
++			ptr += ret;
++			continue;
++		}
++
++		if (ret == 0)
++			break;
++
++		/* ret < 0 */
++		if (errno == EINTR)
++			continue;
++
++		if (errno == EAGAIN) {
++			int pret = __lxc_wait_for_io_ready(fd, POLLOUT, 5000);
++
++			/* we've got an event on fd */
++			if (pret > 0)
++				continue;
++
++			if (pret == -ETIMEDOUT)
++				break;
++
++			if (pret < 0)
++				return ret_set_errno(-1, -pret);
++		}
++
++		/* some other error */
++		return ret_set_errno(-1, errno);
++	}
++
++	return count - left_to_write;
++}
++
+ ssize_t lxc_write_nointr(int fd, const void *buf, size_t count)
+ {
+ 	ssize_t ret;
+diff --git a/src/lxc/file_utils.h b/src/lxc/file_utils.h
+index 4fcaa47855..7d277dad33 100644
+--- a/src/lxc/file_utils.h
++++ b/src/lxc/file_utils.h
+@@ -35,6 +35,8 @@ __hidden extern int lxc_read_from_file(const char *filename, void *buf, size_t c
+     __access_w(2, 3);
+ 
+ /* send and receive buffers completely */
++__hidden extern ssize_t lxc_write_all(int fd, const void *buf, size_t count) __access_r(2, 3);
++
+ __hidden extern ssize_t lxc_write_nointr(int fd, const void *buf, size_t count) __access_r(2, 3);
+ 
+ __hidden extern ssize_t lxc_pwrite_nointr(int fd, const void *buf, size_t count, off_t offset)
+diff --git a/src/lxc/terminal.c b/src/lxc/terminal.c
+index 86fe785b6c..d066c2b9ee 100644
+--- a/src/lxc/terminal.c
++++ b/src/lxc/terminal.c
+@@ -338,7 +338,7 @@ static int lxc_terminal_ptx_io(struct lxc_terminal *terminal)
+ 	w_rbuf = w_log = 0;
+ 	/* write to peer first */
+ 	if (terminal->peer >= 0)
+-		w = lxc_write_nointr(terminal->peer, buf, r);
++		w = lxc_write_all(terminal->peer, buf, r);
+ 
+ 	/* write to terminal ringbuffer */
+ 	if (terminal->buffer_size > 0)
+@@ -375,7 +375,7 @@ static int lxc_terminal_peer_io(struct lxc_terminal *terminal)
+ 		return -1;
+ 	}
+ 
+-	w = lxc_write_nointr(terminal->ptx, buf, r);
++	w = lxc_write_all(terminal->ptx, buf, r);
+ 	if (w != r)
+ 		WARN("Short write on terminal r:%d != w:%d", r, w);
+ 
+
+From 97bd7699c79493f32fcee0f976b6c397cf0f0ad5 Mon Sep 17 00:00:00 2001
+From: Alexander Mikhalitsyn <[email protected]>
+Date: Wed, 21 Jan 2026 18:20:30 +0100
+Subject: [PATCH 2/2] tests/lxc-attach: ensure no data corruption happens
+ during heavy IO on pts
+
+Signed-off-by: Alexander Mikhalitsyn <[email protected]>
+---
+ src/tests/lxc-test-lxc-attach | 65 ++++++++++++++++++++++++++++++++---
+ 1 file changed, 61 insertions(+), 4 deletions(-)
+
+diff --git a/src/tests/lxc-test-lxc-attach b/src/tests/lxc-test-lxc-attach
+index 49289df5d3..720545f994 100755
+--- a/src/tests/lxc-test-lxc-attach
++++ b/src/tests/lxc-test-lxc-attach
+@@ -58,6 +58,7 @@ for i in $(seq 1 100); do
+ 	if [ "$attach" != "busy" ]; then
+ 		FAIL "lxc-attach -n busy -- hostname"
+ 	fi
++	rm -f "${ATTACH_LOG}"
+ done
+ 
+ # stdin  --> /dev/null
+@@ -67,6 +68,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- hostname < /dev/null
+ if [ "$attach" != "busy" ]; then
+         FAIL "lxc-attach -n busy -- hostname < /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> attached to pty
+ # stdout --> /dev/null
+@@ -75,6 +77,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- hostname > /dev/null
+ if [ -n "$attach" ]; then
+         FAIL "lxc-attach -n busy -- hostname > /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> attached to pty
+ # stdout --> attached to pty
+@@ -83,6 +86,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- hostname 2> /dev/null
+ if [ "$attach" != "busy" ]; then
+         FAIL "lxc-attach -n busy -- hostname 2> /dev/null < /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> /dev/null
+ # stdout --> attached to pty
+@@ -91,6 +95,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- hostname 2> /dev/null
+ if [ "$attach" != "busy" ]; then
+         FAIL "lxc-attach -n busy -- hostname 2> /dev/null < /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # Use a synthetic reproducer in container to produce output on stderr. stdout on
+ # the host gets redirect to /dev/null. We should still be able to receive
+@@ -104,6 +109,7 @@ attach=$( ( lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- sh -c 'hostname >&
+ if [ "$attach" != "busy" ]; then
+         FAIL "lxc-attach -n busy -- sh -c 'hostname >&2' > /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # Use a synthetic reproducer in container to produce output on stderr. stderr on
+ # the host gets redirect to /dev/null. We should not receive output on stderr on
+@@ -116,7 +122,7 @@ attach=$( ( lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- sh -c 'hostname >&
+ if [ -n "$attach" ]; then
+         FAIL "lxc-attach -n busy -- sh -c 'hostname >&2' 2> /dev/null"
+ fi
+-
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> attached to pty
+ # stdout --> /dev/null
+@@ -126,7 +132,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- sh -c 'rm 2>&1' > /de
+ if [ -n "$attach" ]; then
+         FAIL "lxc-attach -n busy -- sh -c 'rm 2>&1' > /dev/null"
+ fi
+-
++rm -f "${ATTACH_LOG}"
+ 
+ # - stdin  --> attached to pty
+ # - stdout --> attached to pty
+@@ -136,6 +142,7 @@ attach=$(lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- sh -c 'rm 2>&1' 2> /d
+ if [ -z "$attach" ]; then
+         FAIL "lxc-attach -n busy -- sh -c 'rm 2>&1' 2> /dev/null"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> $in
+ # stdout --> attached to pty
+@@ -144,6 +151,7 @@ attach=$(echo hostname | lxc-attach -n busy -l trace -o "${ATTACH_LOG}" -- || FA
+ if [ "$attach" != "busy" ]; then
+         FAIL "echo hostname | lxc-attach -n busy --"
+ fi
++rm -f "${ATTACH_LOG}"
+ 
+ # stdin  --> attached to pty
+ # stdout --> $out
+@@ -158,7 +166,7 @@ if [ "$outcontent" != "OUT" ] || [ "$errcontent" != "ERR" ]; then
+         FAIL "lxc-attach -n busy -- sh -c 'echo OUT; echo ERR >&2' > $out 2> $err"
+ fi
+ 
+-rm -f $out $err
++rm -f $out $err "${ATTACH_LOG}"
+ 
+ # stdin  --> $in
+ # stdout --> $out
+@@ -174,8 +182,57 @@ if [ "$outcontent" != "busy" ] || [ -z "$errcontent" ]; then
+         FAIL "echo 'hostname; rm' | lxc-attach -n busy > $out 2> $err"
+ fi
+ 
+-rm -f $out $err
++rm -f $out $err "${ATTACH_LOG}"
++
++#
++# This testcase covers cases like:
++# https://github.com/lxc/lxc/issues/4546
++# https://discuss.linuxcontainers.org/t/lxc-attach-long-output-stops-suddenly-possible-bug/22031
++# https://discuss.linuxcontainers.org/t/fixing-forgejo-runners-lxc-logging/25918
++#
++# Idea is simple, we simulate a heavy IO and write relatively large amount of data to overfill
++# pts device buffers, then ensure data integrity.
++#
++# We need to use "script" tool to allocate TTYs properly, otherwise we don't go into a
++# problematic LXC code-path we want to cover.
++#
++# Also, I had to introduce two synthetic sleeps: one before issuing commands to busybox shell inside
++# a container and another one after.
++#
++# First one is needed, because LXC looses some pieces of terminal device input during
++# lxc-attach command initialization because of tcsetattr(fd, TCSAFLUSH, &newtios) call
++# (see https://github.com/lxc/lxc/blob/5d9839bc1316fa185d8c29b90982684b32e3dfa7/src/lxc/terminal.c#L523)
++# I would replace TCSAFLUSH with TCSANOW to avoid TTY buffer flush (and I tested that it helps),
++# but taking into account that this code is here since 2010
++# (see https://github.com/lxc/lxc/commit/e0dc0de76ed1ad9e284a37bd01268227d4eae8c9)
++# I decided to keep it like it is for now (FIXME?).
++#
++# Second sleep is needed because of a bug in busybox, unfortunately, without this sleep,
++# busybox fails to react on the host pipe write-end closure (after full command submission)
++# and continues to poll infinitely. This sleep makes pipe closure even to be separated from
++# a heavy IO and avoids this bug.
++#
++
++# Check test dependencies
++command -v script >/dev/null 2>&1 || { echo "'script' command is missing" >&2; exit 1; }
++busybox dd --help >/dev/null 2>&1 || FAIL "missing busybox's dd applet"
++busybox hexdump --help >/dev/null 2>&1 || FAIL "missing busybox's hexdump applet"
++busybox tee --help >/dev/null 2>&1 || FAIL "missing busybox's tee applet"
++
++out=$(mktemp /tmp/out_XXXX)
++BS=1000000
++( sleep 3; echo "echo DATASTART ; dd if=/dev/urandom bs=$BS count=1 status=none | hexdump | tee /root/large-data.txt ; echo DATAEND" ; sleep 1 ) | \
++	script -q -e -c "lxc-attach -n busy -l trace -o \"${ATTACH_LOG}\"" | \
++	sed -n '/DATASTART/,/DATAEND/{/DATASTART/d;/DATAEND/d;s/[\r\n]*$//;p}' > $out
++
++[ $(stat -c%s $out) -gt $BS ] || FAIL "generated file size is too small"
++cmp -s /var/lib/lxc/busy/rootfs/root/large-data.txt $out || FAIL "data corruption detected"
++
++md5sum /var/lib/lxc/busy/rootfs/root/large-data.txt $out
++ls -lah /var/lib/lxc/busy/rootfs/root/large-data.txt
++rm -f /var/lib/lxc/busy/rootfs/root/large-data.txt $out "${ATTACH_LOG}"
+ 
++# Cleanup stage
+ lxc-destroy -n busy -f
+ rm -f "${ATTACH_LOG}" || true
+ 
diff -Nru lxc-6.0.4/debian/patches/series lxc-6.0.4/debian/patches/series
--- lxc-6.0.4/debian/patches/series	2025-12-26 19:02:22.000000000 +0000
+++ lxc-6.0.4/debian/patches/series	2026-03-02 19:05:00.000000000 +0000
@@ -7,3 +7,4 @@
 0102-cherry-pick-apparmor-generation.patch
 0103-cherry-pick-fix-dbus-reboots.patch
 0104-Add-lxc-net-as-dependency-in-sysvinit-script.patch
+0105-cherry-pick-fix-heavy-io-pts.patch

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to