Add optional uint64 timerslack_ns field (= 17) to thread_core_entry.
This allows CRIU to save and restore the per-thread timer slack value
(controlled via PR_SET_TIMERSLACK / PR_GET_TIMERSLACK) across
checkpoint/restore cycles.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Add an "AI-assisted contributions" section to CONTRIBUTING.md
describing the Assisted-by tag format for commits that involve
AI coding assistants, following the Linux kernel guidance at
docs.kernel.org/process/coding-assistants.html.
Also add concise instructions to GEMINI.md (symlinked to
CLAUDE.md) so that AI agents use the Assisted-by tag instead
of free-form attribution lines.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
Add a test that exercises the broken read lease restore path. The test:
1. Opens a file O_RDONLY and takes a read lease (F_RDLCK).
2. Triggers the breaking sequence by attempting a conflicting
O_WRONLY | O_NONBLOCK open (which returns EWOULDBLOCK).
3. Forks a child to verify post-restore lease state.
4. Calls test_daemon() / test_waitsig() to go through C/R.
5. After restore, the child verifies that the lease type is F_UNLCK
(the expected target type for a broken read lease), confirming
that the breaking sequence was correctly re-established.
Without the preceding fix, restore_lease_prebreaking_state() would set a
write lease (F_WRLCK) instead of a read lease on the read-only fd,
causing either a restore failure (EACCES) or a wrong lease type
post-restore. This test fails without the fix and passes with it.
Signed-off-by: Andrei Vagin <avagin@google.com>
When restoring a breaking lease, CRIU needs to first restore the
pre-breaking state of the lease and then trigger the break again.
Previously, restore_lease_prebreaking_state() was incorrectly using the
file descriptor type (e.g., FD_TYPES__REG) instead of the lease type to
determine which lease to set. This caused it to use O_RDONLY/O_WRONLY
logic on a value that wasn't an open flag.
Fix this by:
1. Passing the target lease type (with the LEASE_BREAKING bit cleared)
to restore_lease_prebreaking_state().
2. Updating set_file_lease() to optionally suppress error messages when
a lease cannot be set (EWOULDBLOCK), which happens when we try to
guess the original lease type.
3. In restore_lease_prebreaking_state(), try to set a read lease first
if the target type is F_UNLCK. If it fails, or if the target type is
F_RDLCK, try a write lease.
This ensures that we correctly attempt to restore the original lease
type even though it's not explicitly stored in the image when it's
in the process of being broken.
Reported-by: Prateek Singh Rathour <rathourprateek8@gmail.com>
Signed-off-by: Andrei Vagin <avagin@google.com>
When running tests in parallel (-p 2) with GCOV=1, multiple
forked processes race to create ../criu.tree. The check-then-
create pattern (os.access + os.mkdir) allows two processes to
both see the directory as missing and then both attempt mkdir,
causing FileExistsError in the second process.
Replace with os.makedirs(exist_ok=True) which handles the race
atomically.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
Upstream Nixpkgs shadows Google's version of the default protobufs,
which is no longer applicable given that they are copied into the
repository, and causes builds to fail.
Signed-off-by: Karim Vergnes <me@thesola.io>
This allows users to directly use the criu package declared in the flake
as a bleeding-edge substitute to the one found in Nixpkgs, and leverage
the existing NixOS testing infrastructure with their development version.
Signed-off-by: Karim Vergnes <me@thesola.io>
When the amdgpu plugin is called for restore of a file img,
we don't initially know if it's a kfd or drm file. So we first
try to open a kfd file, and if that fails, we assume it's
a drm file. That failure leads to a lot of error messages like
Error amdgpu_plugin: amdgpu-kfd-141.img: Failed to open for read
For this normal behaviour.
Add an option on open_img_file to suppress this error when
we aren't sure if the file we're trying to open exists.
Signed-off-by: David Francis <David.Francis@amd.com>
open_img_file can return null if, for example, the dump file
isn't present. Tehre were three places where this wasn't checked.
Check it.
Signed-off-by: David Francis <David.Francis@amd.com>
Default to the pure-python protobuf implementation
on Python 3.14+ when the user has not explicitly
selected a backend. This avoids known protobuf
C-extension import failures seen in some distro
packaging setups.
Fixes#2848
Signed-off-by: Farzan Aman Khan <farzanaman99@gmail.com>
archlinux:latest is a rolling-release image and pacman -Syu pulls
the latest packages on every build. Failures caused by upstream
package churn or mirror outages are beyond CRIU's control and
should not block merges.
The cross-compile job already uses continue-on-error: true for
its experimental targets; apply the same treatment to
archlinux-test.
Fixes: #2911
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
With GCOV=1, zdtm.py passes the CRIU workspace parent as an
external bind-mount into the test namespace (ZDTM_CRIU). On
restore in the uns flavor CRIU calls open_tree(OPEN_TREE_CLONE)
on that path. Because the workspace is a parent of the user
namespace mount tree, its mounts are MNT_LOCKED, and open_tree
returns EINVAL.
Fix by creating a private bind-mount of the workspace parent at
'../criu.tree' before each test run, then using that path as the
source of the bind-mount inside the ZDTM namespace (ns.c). On
restore, point the external mapping at 'criu.tree' instead of
the raw workspace path.
Because criu.tree is MS_PRIVATE and is created before any CRIU
run, cgroupfs submounts that CRIU later creates inside the
workspace (cg_yard via mkdtemp) do not propagate into it.
open_tree(OPEN_TREE_CLONE, criu.tree) therefore sees no locked
submounts and succeeds.
This replaces the earlier --work-dir workaround, which was
correct in principle but broke the RPC test path where
criu_rpc.__set_opts has no handler for --work-dir.
Fixes: #2911
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Two problems made gcov-test unreliable:
1. The gcov step ran with --max-procs 4, causing multiple gcov
processes to concurrently read and update the same .gcda files.
GCC does not protect these files against concurrent access, so
this can silently corrupt coverage data or produce
non-deterministic failures. Drop --max-procs to serialize gcov.
2. 'make codecov' curls the uploader binary from
https://uploader.codecov.io/latest/linux/codecov at job
runtime. This fails on Codecov CDN outages and on pull requests
from forks where CODECOV_TOKEN is not forwarded. Replace this
step with the pinned codecov/codecov-action@v5, which avoids
the runtime curl and handles both authenticated and
unauthenticated cases gracefully.
Fixes: #2911
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Use fully qualified image names (e.g. "docker.io/...") in Dockerfiles
instead of relying on implicit registry resolution. This makes the
registry explicit and avoids ambiguity in environments where Docker
and Podman may use different default registries.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
The scripts installing dependencies are now copied and executed before
copying the full source tree. This allows to cache the dependency
installation layers as long as these scripts remain unchanged.
Previously, copying the entire repository invalidated the cache on every
source change. While our CI intentionally doesn't preserve cached layers
across independent runs, the changes in this patch help with local testing
when investigating issues.
Examples:
- sudo -E make -C scripts/ci alpine GCC=1
- sudo -E make -C scripts/ci alpine CLANG=1
Suggested-by: Andrei Vagin <avagin@google.com>
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Use the new consolidated GitHub Actions workflow (ci.yml)
instead of individual workflow badges.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
The check-commit target verifies that each commit builds successfully
on its own. However, without cleaning previously generated build artifacts
(e.g. auto-generated `*.o` files) this can cause the check to fail:
make[2]: *** No rule to make target 'compel/include/uapi/compel/asm/breakpoints.h', needed by 'compel/arch/x86/src/lib/infect.o'. Stop.
make[1]: *** [Makefile.compel:35: compel/libcompel.a] Error 2
To fix this, we add 'git clean -dfx' to remove all untracked and ignored
files before attempting the build.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Update the state machine to track entry and exit stages for all
syscalls, not just the target one. This allows skipping
ptrace_get_regs() calls on the exit stage of syscalls.
Signed-off-by: Andrei Vagin <avagin@google.com>
The restorer used a futex to synchronize threads right before sigreturn.
This could lead to a deadlock when compel_stop_tasks_on_syscall executes
all restored tasks/threads sequentially, allowing each to execute one
syscall per iteration. One task might wait for another one that hasn't
had a chance to run yet.
To avoid this, this patch replaces the futex with an atomic variable.
The last thread to finish the restoration process handles the final
cleanup tasks, such as closing the log and unmapping restorer memory.
Signed-off-by: Andrei Vagin <avagin@google.com>
compel_stop_on_syscall is only ever called for a single task. Simplify
its implementation by removing the 'tasks' parameter and the associated
logic for handling multiple tasks. It now takes a 'pid' directly and
waits for that specific task to exit the required syscall.
Signed-off-by: Andrei Vagin <avagin@google.com>
Hardware breakpoints were originally intended to speed up the resume process
by stopping the process at a specific point in the pie code. However, it
turned out that they don't provide a significant speedup and, in some cases,
can even slow it down. This is especially critical for hosts with a large
number of CPUs.
Hardware Breakpoint Restore Performance Benchmark
==================================================
System: Linux 6.17.0-1007-gcp x86_64
CPU: INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.10GHz
Virt: google
CRIU: Version: 4.2
Iterations per data point: 5
Threads With BP (us) Without BP (us) Diff (%)
-------- ------------ --------------- --------
>>> Benchmarking with 1 thread(s)...
1 391 326 19.9%
>>> Benchmarking with 10 thread(s)...
10 1098 695 58.0%
>>> Benchmarking with 50 thread(s)...
50 3772 2344 60.9%
>>> Benchmarking with 100 thread(s)...
100 6740 4504 49.6%
>>> Benchmarking with 500 thread(s)...
500 31382 19982 57.1%
>>> Benchmarking with 1000 thread(s)...
1000 58617 40568 44.5%
Notes:
'With BP' = hardware breakpoints enabled (current default)
'Without BP' = CRIU_FAULT=130 (FI_NO_BREAKPOINTS, uses PTRACE_SYSCALL)
Positive diff% means breakpoints are slower
Signed-off-by: Andrei Vagin <avagin@google.com>
Splitting the ptrace interrupt and wait loops allows us to send the
interrupt to all tasks and threads first, so they can start stopping in
parallel while we're still sending interrupts to the remaining ones.
This should improve restore performance for large process trees.
The redundant parse_threads calls in catch_tasks are removed as threads
are already parsed in attach_to_tasks which is called earlier.
Signed-off-by: Andrei Vagin <avagin@google.com>
When CRIU traces a large number of tasks, calling wait4(-1, ...) can
be slow because the kernel has to iterate over many tasks to find one
that has changed state. This overhead becomes significant when
thousands of tasks are involved. The time complexity of the old approach
was O(n^2) because we called wait4(-1, ...) N times, and each call
took O(N) time in the kernel.
This patch introduces compel_stop_tasks_on_syscall which takes an
array of PIDs and waits for each one specifically. This avoids the
O(N) search in the kernel's do_wait implementation and significantly
improves performance during the restore phase when many tasks are
being resumed and stopped at the rt_sigreturn syscall.
Some parallelism still exists in this approach. The loop waits for each
process to stop on a syscall, verifies it, and then lets it continue
running. While we move to wait for the next process in the array, the
previously processed ones continue their execution in parallel.
The performance profile of the bottleneck showed:
- 70.45% entry_SYSCALL_64
- do_syscall_64
- 68.29% __do_sys_wait4
- kernel_wait4
- 68.28% do_wait
- 68.26% __do_wait
- 65.69% wait_consider_task
8.71% _raw_spin_lock_irq
2.39% _raw_spin_unlock_irq
Signed-off-by: Andrei Vagin <avagin@google.com>
Commit 6b29103 adds the function read_user_ns_img to read user namespace
image. It looks up the user namespace by id. However, user namespace
can not be found because only mnt, net and pid namespaces are added to
the nsid list. The user namespace is not read from pstree ids.
This commit adds user namespace to the nsid list from the pstree ids
image.
Fixes: 6b29103 ("criu: fix double-open of userns image in --stream mode")
Fixes: #2938
Signed-off-by: Efim Verzakov <efimverzakov@gmail.com>
VMA_AREA_GUARD areas are added to the end of the vma_area_list and
are not necessarily sorted by address. pmc_fill_cache() expects
VMAs to be sorted and triggers BUG_ON() if it meets a VMA with
a lower address than the current cache window.
Since guard VMAs don't have pagemap entries and are skipped during
dump, just stop the lookahead loop when we encounter one.
Signed-off-by: Andrei Vagin <avagin@gmail.com>
restore_userns_binfmt_misc() was opening the userns image a second
time, but criu-image-streamer rejects duplicate file requests:
criu-image-streamer Error: CRIU is requesting the image file
userns-15.img multiple times. This is not allowed to keep the
memory usage low
Fix by pre-reading the image once in a new read_user_ns_img()
helper, called from prepare_namespace_before_tasks() which runs
before the first clone(). Following the same pattern as NetnsEntry
on net namespace ids, the UsernsEntry is stored in a new 'user.e'
field on the ns_id structure for the user namespace. Both the CRIU
coordinator (prepare_userns) and the forked root task
(restore_userns_binfmt_misc) look up the ns_id and access the
entry from there -- no second open_image() call is needed.
Tested with:
sudo ./test/zdtm.py run --stream -t zdtm/static/env00 --ignore-taint
sudo ./test/zdtm.py run --stream -t zdtm/static/env00 --ignore-taint -f uns
Both pass with the fix applied.
Fixes: e6510a338a ("criu: Support binfmt_misc sandboxing")
Fixes: #2924
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Install libcriu into a local staging directory (.install/) using
'make install-lib' and reference only that directory during build
and test:
CFLAGS := -I$(INSTALL_DIR)/include/criu
LDFLAGS := -L$(INSTALL_DIR)/lib -lcriu
LD_LIBRARY_PATH=${MAIN_DIR}/.install/lib
Nothing from the non-test part of the repo is used at compile,
link, or run time.
The compile rule is also corrected to use $< instead of $^
for gcc -c.
Fixes: #2927
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
gcc doesn't report any warnings if there are two identical enums with the
same name. Actually, we wanted to detect if a specific enum is already
exists. By using a different member name in the test, we ensure that a
conflict occurs if the enum is already defined in the system headers.
Signed-off-by: Andrei Vagin <avagin@gmail.com>
Rework GitHub workflows to consolidate all test-related jobs into a
single ci.yml file. This ensures that the alpine-test job runs first,
and all other tests are executed only if it passes.
This reduces redundant triggers and provides a clear dependency path
for CI runs.
Signed-off-by: Andrei Vagin <avagin@gmail.com>
pr_perror already appends a newline; the explicit \n in the
format string violates the lint rule and breaks CI.
Fixes: 555b257c5e77 ("plugin/amdgpu: Catch error for failure to open drm device")
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
kfd_criu_device_bucket.drm_fd is an unsigned int, so the comparison
device_bucket->drm_fd < 0
was always false.
Fix that, and also make the error message a bit more descriptive.
Signed-off-by: David Francis <David.Francis@amd.com>
Fix several memory leaks in sk-queue error handling:
- collect_one_packet(): Free pkt->data when n_scm > 1 check fails.
- collect_one_packet(): Move list_add_tail() after read_img_buf() so
that a packet with freed data is never left on the list.
- dump_scm_rights(): Free scme when dump_my_file() fails. The
allocation from xmalloc() was leaked on this error path.
- send_one_pkt(): Free pkt->scm (allocated in prepare_scms()) after
sendmsg(), alongside the existing xfree(pkt->data).
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
After recvmsg() with MSG_PEEK, dump_sk_queue() checks for MSG_TRUNC
(data truncation) but not MSG_CTRUNC (control data truncation). When
the control message buffer is too small, the kernel sets MSG_CTRUNC
and silently discards the overflowing ancillary data. For SCM_RIGHTS,
this means passed file descriptors are lost without any error.
Add a MSG_CTRUNC check right after the existing MSG_TRUNC check so
that dump fails explicitly instead of silently producing an incomplete
image.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
We saw compilation errors like:
error: call to ‘__open_missing_mode’ declared with attribute error:
open with O_CREAT or O_TMPFILE in second argument needs 3 arguments
Let's fix them by adding mode 0777 everywhere.
Before this change the mode was taken randomly from stack (according to
man 2 open) and that is likely not what we want.
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
prepare_cgroup_namespace() can call userns_call() to move
the task into a cgroup. userns_call() uses
sendmsg() with SCM_CREDENTIALS to communicate with usernsd.
If the user namespace maps are configured such that the parent's UID
(typically 0) is not mapped to the same UID in the child namespace
(i.e., lower_first != 0), the getuid() syscall in the child will return
the overflow UID (65534) until the child's credentials are explicitly
set within the namespace.
When sendmsg() is called with SCM_CREDENTIALS containing the overflow UID,
the kernel's __scm_send function returns -EINVAL, causing the restore
to fail.
[avagin: tweaked the commit message]
Signed-off-by: Efim Verzakov <efimverzakov@gmail.com>
Signed-off-by: Andrei Vagin <avagin@gmail.com>
In unix_collect_one(), icons (in-flight connections) are added to the
global unix_listen_icons hash table. Previously, if xzalloc() failed
mid-loop while allocating an icon, the code could leave partially
inserted icons in the global hash, resulting in dangling pointers and
potential use-after-free crashes.
This patch changes the logic to pre-allocate all unix_sk_listen_icon
nodes before inserting any of them into unix_listen_icons. If allocation
fails, the temporary list is freed and the function aborts before
mutating the global hash. This ensures that the hash is only updated
when all allocations succeed, and avoids the need to traverse and remove
icons from the hash on error.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Pavel Emelyanov, the founder and long-time leader of the CRIU project,
is moving to a retired role. As the project's inventor, Pavel's vision
and leadership over many years were fundamental in bringing CRIU to its
current status as a production-ready solution.
Andrey Vagin is appointed as the chief maintainer.
Signed-off-by: Andrei Vagin <avagin@gmail.com>
We need to support restoring processes on different nodes or subnets
where the original socket IPs may no longer be valid. This change
introduces a new plugin hook, CR_PLUGIN_HOOK__UPDATE_INETSK, and wires
it into both the plugin API and the legacy descriptor autogen so that
existing plugins remain compatible.
The hook is invoked from collect_one_inetsk() before address validation,
port reservation, or bind()/connect(). A plugin may rewrite the IPv4 or
IPv6 src/dst address fields in InetSkEntry in‑place and return 0.
Returning -ENOTSUP skips to the next plugin; any other negative value
aborts the restore for that socket. Only the IP addresses may change —
family, ports, ifname and length constraints remain enforced.
This mechanism is mainly intended for cross‑node or Kubernetes‑style
scenarios where the final IP is only known after the target network
namespace is created and configured. Pre‑editing image files would be
awkward or infeasible otherwise.
Note: for established TCP connections, external coordination or preserving
the original 4‑tuple is still required. This hook only adjusts addresses
and does not modify TCP state.
Signed-off-by: Rowan-Ye <rowenye1@gmail.com>
mnt_id was declared as uint32_t but assigned -1 when absent, which yields
UINT32_MAX. When passed to mntns_get_root_by_mnt_id(int), the conversion
from UINT32_MAX to int is implementation-defined and can become a large
positive value, potentially causing lookup_nsid_by_mnt_id() to return NULL
and trigger BUG_ON().
Change mnt_id to int with an explicit cast from f_handle->mnt_id when
present, so the sentinel -1 is preserved reliably across platforms.
Signed-off-by: 3idey <elaidya225@gmail.com>
Add checks for mntns_get_root_by_mnt_id() return value before using it
in openat() calls. This prevents using an invalid file descriptor if
the mount namespace root lookup fails.
Add error logging when mntns_get_root_by_mnt_id() fails to provide
diagnostic context about which mount namespace and path were being
accessed.
Also rename local variable 'path' to 'fpath' to avoid shadowing the
outer scope variable of the same name.
Signed-off-by: 3idey <elaidya225@gmail.com>
Before that patch we always had exactly the same magic and extentsion
patterns generated.
While on it let's fix the data restrictions:
- The length of extension and magic should be non-zero.
- Let's explicitly wrap extension characters with 256.
Fixes: #2886
Co-developed-by: Andrei Vagin <avagin@google.com>
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
In ns flavor for instance the binfmt_misc is not c/r-ed, but without
this cleanup the test will not detect that as it would inherit correct
unchanged binfmt_misc step which was there on dump.
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
In zdtm/static/binfmt_misc we print the string which contains random
bytes, some of which may not map to utf-8. So let's ignore those bytes
we can't show in utf-8.
Else we can get:
File ".../criu/test/zdtm.py", line 660, in print_output
print(output.read())
~~~~~~~~~~~^^
File "<frozen codecs>", line 325, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe3 in position 180: invalid continuation byte
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>