From 8f315e71565333ebea96ebf2b863ba14859bb39c Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Wed, 13 May 2026 15:30:05 -0700 Subject: [PATCH 1/2] timer: skip POSIX timers targeting dead threads When a POSIX timer uses SIGEV_THREAD_ID and the target thread has exited, dump fails in PID namespaces ("Unable to convert the notify thread id") and restore fails without PID namespaces (EINVAL from timer_create()). We noticed this when an application switched from Java 8 to Java 21. Detect dead notify threads during dump and skip the timer instead of failing. The kernel already silently drops signals from these timers, so skipping them during dump is safe. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Felicitas Pojtinger --- criu/timer.c | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/criu/timer.c b/criu/timer.c index 856501be6..b8c0e32f4 100644 --- a/criu/timer.c +++ b/criu/timer.c @@ -306,6 +306,16 @@ static int encode_notify_thread_id(pid_t rtid, struct pstree_item *item, PosixTi if (!(root_ns_mask & CLONE_NEWPID)) { /* Non-pid-namespace case */ + for (i = 0; i < item->nr_threads; i++) { + if (item->threads[i].real == rtid) + break; + } + + if (i == item->nr_threads) { + pr_warn("Skipping timer because notify thread %d is dead\n", rtid); + return 1; + } + pte->notify_thread_id = rtid; pte->has_notify_thread_id = true; return 0; @@ -326,8 +336,8 @@ static int encode_notify_thread_id(pid_t rtid, struct pstree_item *item, PosixTi } if (vtid == 0) { - pr_err("Unable to convert the notify thread id %d\n", rtid); - return -1; + pr_warn("Skipping timer because notify thread %d is dead\n", rtid); + return 1; } pte->notify_thread_id = vtid; @@ -351,10 +361,7 @@ static int encode_posix_timer(struct pstree_item *item, struct posix_timer *v, s pte->vsec = v->val.it_value.tv_sec; pte->vnsec = v->val.it_value.tv_nsec; - if (encode_notify_thread_id(vp->spt.notify_thread_id, item, pte)) - return -1; - - return 0; + return encode_notify_thread_id(vp->spt.notify_thread_id, item, pte); } int parasite_dump_posix_timers_seized(struct proc_posix_timers_stat *proc_args, struct parasite_ctl *ctl, @@ -367,7 +374,7 @@ int parasite_dump_posix_timers_seized(struct proc_posix_timers_stat *proc_args, struct parasite_dump_posix_timers_args *args; int ret, exit_code = -1; int args_size; - int i; + int i, j; if (core_alloc_posix_timers(tte, proc_args->timer_n, &pte)) return -1; @@ -386,14 +393,28 @@ int parasite_dump_posix_timers_seized(struct proc_posix_timers_stat *proc_args, if (ret < 0) goto end_posix; + /* + * i advances every iteration (indexes parasite-filled args->timer[]); + * j advances only for timers we keep, both the next output slot + * and the final count. Timers whose notify thread has exited are + * skipped (i.e., j <= i). + */ i = 0; + j = 0; list_for_each_entry(temp, &proc_args->timers, list) { - posix_timer_entry__init(&pte[i]); - if (encode_posix_timer(item, &args->timer[i], temp, &pte[i])) + posix_timer_entry__init(&pte[j]); + ret = encode_posix_timer(item, &args->timer[i], temp, &pte[j]); + if (ret < 0) goto end_posix; - tte->posix[i] = &pte[i]; + if (ret == 1) { + i++; + continue; + } + tte->posix[j] = &pte[j]; i++; + j++; } + tte->n_posix = j; exit_code = 0; end_posix: From 5b30f6d5a003dce48dcf24aa0d7e9a376cf9123d Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Wed, 13 May 2026 15:30:10 -0700 Subject: [PATCH 2/2] zdtm: check POSIX timers targeting dead threads are skipped Add a test that creates a SIGEV_THREAD_ID POSIX timer pointing at a thread which then exits before checkpoint. Dump and restore must succeed by skipping the timer rather than failing. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Felicitas Pojtinger --- test/zdtm/static/Makefile | 2 + test/zdtm/static/dead_thread_posix_timer.c | 70 +++++++++++++++++++ test/zdtm/static/dead_thread_posix_timer.desc | 1 + 3 files changed, 73 insertions(+) create mode 100644 test/zdtm/static/dead_thread_posix_timer.c create mode 100644 test/zdtm/static/dead_thread_posix_timer.desc diff --git a/test/zdtm/static/Makefile b/test/zdtm/static/Makefile index 947cafd37..47a5fdc7b 100644 --- a/test/zdtm/static/Makefile +++ b/test/zdtm/static/Makefile @@ -76,6 +76,7 @@ TST_NOFILE := \ pthread_timers \ pthread_timers_h \ leader_posix_timer \ + dead_thread_posix_timer \ rseq00 \ membarrier \ vdso00 \ @@ -618,6 +619,7 @@ pthread02: LDLIBS += -pthread pthread_timers: LDLIBS += -lrt -pthread pthread_timers_h: LDLIBS += -lrt -pthread leader_posix_timer: LDLIBS += -lrt -pthread +dead_thread_posix_timer: LDLIBS += -lrt -pthread different_creds: LDLIBS += -pthread sigpending: LDLIBS += -pthread sigaltstack: LDLIBS += -pthread diff --git a/test/zdtm/static/dead_thread_posix_timer.c b/test/zdtm/static/dead_thread_posix_timer.c new file mode 100644 index 000000000..afd973eba --- /dev/null +++ b/test/zdtm/static/dead_thread_posix_timer.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include + +#include "zdtmtst.h" + +#ifndef SIGEV_THREAD_ID +#define SIGEV_THREAD_ID 4 +#endif + +const char *test_doc = "Check that a SIGEV_THREAD_ID timer targeting a dead thread doesn't break dump"; +const char *test_author = "Felicitas Pojtinger "; + +static timer_t timerid; + +static void *worker(void *arg) +{ + pid_t tid; + struct sigevent evp = {}; + + tid = syscall(SYS_gettid); + + evp.sigev_notify = SIGEV_THREAD_ID; +#ifdef __GLIBC__ + evp._sigev_un._tid = tid; +#else + evp.sigev_notify_thread_id = tid; +#endif + evp.sigev_signo = SIGRTMIN; + + if (timer_create(CLOCK_MONOTONIC, &evp, &timerid)) { + pr_perror("timer_create"); + return (void *)1; + } + + return NULL; +} + +int main(int argc, char **argv) +{ + pthread_t thr; + void *ret; + + test_init(argc, argv); + + if (pthread_create(&thr, NULL, worker, NULL)) { + pr_perror("pthread_create"); + return 1; + } + + if (pthread_join(thr, &ret)) { + pr_perror("pthread_join"); + return 1; + } + + if (ret != NULL) { + fail("Timer creation thread failed"); + return 1; + } + + test_daemon(); + test_waitsig(); + + pass(); + + timer_delete(timerid); + return 0; +} \ No newline at end of file diff --git a/test/zdtm/static/dead_thread_posix_timer.desc b/test/zdtm/static/dead_thread_posix_timer.desc new file mode 100644 index 000000000..c0a31e06b --- /dev/null +++ b/test/zdtm/static/dead_thread_posix_timer.desc @@ -0,0 +1 @@ +{'feature': 'ns_pid'}