Package: sane-backends
Version: sane-backends
Severity: normal
Tags: patch upstream
Package: src:sane-backends
Version: 1.4.0-1
Severity: normal
Tags: patch upstream fixed-upstream
Dear Maintainer,
sanei/sanei_thread.c enables PTHREAD_CANCEL_ASYNCHRONOUS in local_thread(), so
a reader thread cancelled through sanei_thread_kill() is unwound at an
arbitrary instruction rather than at a defined cancellation point. I
discovered this problem while examining the xsane source code and attempting to
fix issues there. This issue has existed since 1.0.13 (2003).
Upstream fixed this in commit 8acc267d ("sanei_thread.c: Use deferred
cancellation mode", MR !881), merged 2025-07-14. That landed seven weeks after
the 1.4.0 tag (2025-05-24) and no release has followed, so every released
sane-backends still carries it -- including 1.4.0-1 in unstable and 1.3.1-3 in
trixie. A patch is attached; it is a straight cherry-pick. I would rather
submit this now and prevent future issues vs having to be reactive towards this
issue.
Upstream's commit message describes the Linux symptom as a hang, with this
reproducer:
while true; do scanimage --verbose --device=test --batch-count=2; done
"The command hangs after several iterations." Upstream cites perl-Image-Sane's
test suite as the affected consumer.
HONESTY ABOUT REPRODUCTION ON DEBIAN/UBUNTU
I could not reproduce the hang on glibc: 40 iterations of the above on Ubuntu
24.04 (1.2.1-7build4) completed cleanly. glibc's unwinder appears to tolerate
asynchronous cancellation well, so on Debian this is a latent defect rather
than one users are likely hitting today. I am reporting it because the code is
unambiguously wrong, the fix is upstream and trivial, and the failure is a race
-- a passing run proves nothing.
WHERE IT IS NOT LATENT
The same code is fatal on Windows, where winpthreads implements asynchronous
cancellation by force-unwinding the target thread via setjmp/RtlUnwind. Built
for mingw-w64 (MSYS2 ships sane-backends 1.4.0), a scan segfaults at end of
image in roughly nine runs out of ten:
#6 local_thread (arg=...) at sanei_thread.c:360
#5 libwinpthread-1.dll
#3 ucrtbase!.intrinsic_setjmpex
#2 ntdll!RtlUnwind -> SIGSEGV
With 8acc267d applied, a test-backend scan driven directly through libsane went
from about 1 run in 10 succeeding to 20 out of 20 clean, reading the full image
every time (48 reads, 278480 bytes at 590x472 grey/150dpi).
This is mentioned only as evidence that the defect is real and that the fix
works; I am not asking Debian to care about Windows. It does mean sanei_thread
is shared machinery used by real hardware backends, not just the test backend,
so the exposure is not limited to test setups.
RELATED
Upstream issue #816 ("Should APPLE/MACH be using SIGUSR2 to kill a thread?")
touches the same function and is still open:
https://gitlab.com/sane-project/backends/-/work_items/816
Debian #869318 ("libimage-sane-perl: FTBFS randomly: build hangs during tests",
serious, 2017) is, I believe, the same defect seen from the other end. It is
exactly the reproducer upstream describes -- a perl-Image-Sane test suite
hanging intermittently -- but it was filed against the consumer, never
reassigned here, and closed by working around it in libimage-sane-perl/5-3. Its
log mentions neither pthread nor cancellation. It is not a duplicate of this
report; it is evidence that the race has been costing people time for years
while presenting as a flaky test.
I searched the BTS (src:sane-backends, 13 open bugs), Ubuntu, and upstream
before filing; this defect does not appear to have been reported anywhere.
A 1.4.1 point release carrying 8acc267d would fix this for every distribution
at once, which may be worth raising with upstream. In my opinion, though, If
debian just adds this to the patch list, this will prevent future issues.
-- System Information (fill in with reportbug, or edit by hand)
-- System Information:
Debian Release: trixie/sid
APT prefers noble-updates
APT policy: (500, 'noble-updates'), (500, 'noble-security'), (500, 'noble'),
(100, 'noble-proposed'), (100, 'noble-backports')
Architecture: amd64 (x86_64)
Foreign Architectures: i386
Kernel: Linux 6.17.0-1031-nvidia (SMP w/16 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE,
TAINT_UNSIGNED_MODULE
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled
From: Aaron Yourk <[email protected]>
Date: Mon, 24 Aug 2026 00:00:00 +0000
Subject: [PATCH] sanei_thread: use deferred cancellation mode
Cherry-pick of upstream commit 8acc267d (MR !881, merged 2025-07-14),
"sanei_thread.c: Use deferred cancellation mode".
local_thread() enabled PTHREAD_CANCEL_ASYNCHRONOUS, so a thread cancelled
via sanei_thread_kill() is unwound at an arbitrary instruction rather than
at a defined cancellation point. Upstream observed this as a hang under
rapid scanimage loops; on Windows/winpthreads the same code segfaults.
Deferred cancellation -- the glibc default -- still cancels, just safely.
Upstream: https://gitlab.com/sane-project/backends/-/commit/8acc267d
---
sanei/sanei_thread.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
--- a/sanei/sanei_thread.c
+++ b/sanei/sanei_thread.c
@@ -333,6 +333,10 @@ local_thread( void *arg )
static int status;
pThreadDataDef ltd = (pThreadDataDef)arg;
+ /* Set signal handlers for Mac and Mach. On other systems,
+ * thread cancellation is enabled in deferred mode,
+ * which is glibc default.
+ */
#if defined (__APPLE__) && defined (__MACH__)
struct sigaction act;
@@ -340,11 +344,6 @@ local_thread( void *arg )
act.sa_flags = 0;
act.sa_handler = thread_exit_handler;
sigaction( SIGUSR2, &act, 0 );
-#else
- int old;
-
- pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &old );
- pthread_setcanceltype ( PTHREAD_CANCEL_ASYNCHRONOUS, &old );
#endif
DBG( 2, "thread started, calling func() now...\n" );