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
signature.asc
Description: This is a digitally signed message part

