Several error paths in amdgpu_plugin_drm_dump_file() leak
resources. The drmPrimeHandleToFD() and open_img_file() failures
use break instead of goto exit, which skips per-iteration cleanup
and lets the function continue dumping metadata with a stale ret
value. Replace break with proper resource cleanup and goto exit.
The vm_info_entries buffer is not freed when the ioctl or
allocate_vm_entries() calls fail after allocating it, and the
buffer from posix_memalign() is never freed after sdma_copy_bo(),
leaking memory on every loop iteration. Add the missing xfree()
calls.
Check the amdgpu_device_initialize() return value because h_dev
is undefined on failure. Move list_handles_entries cleanup into
the exit block because error paths that goto exit skip the
existing xfree() call after the loop. Add a NULL check before
free_e(rd) because rd allocation can fail before the struct is
initialized.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Initialize gpu_ids, fds_write, and entries to NULL before
allocating them in init_parallel_restore_cmd() and
init_parallel_restore_cmd_by_head(). The caller declares the
struct on the stack without an initializer, so these members
hold undefined values. If a later xzalloc() fails and the
caller invokes free_parallel_restore_cmd(), it will call
xfree() on those undefined pointers.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Call amdgpu_device_deinitialize() before continuing when
get_gem_handle() fails and before returning when a matching
handle is found. Both paths skip the existing deinitialize
call at the end of the loop body, leaking the device handle.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
open_img_file() can return NULL after a read_fp() or write_fp()
failure without setting errno. The openat() and fdopen() paths
set errno, but the read/write path does not, so callers using
-errno get zero or a stale value. Set errno to EIO on this path
so errno is always valid when the function returns NULL.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Close the file descriptor when fdopen() fails, as fdopen does not
take ownership of the fd on failure.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
The exit label in amdgpu_plugin_drm_restore_file() returns
immediately when ret < 0 without freeing the dmabufs array or
deinitializing the amdgpu device handle. Move cleanup before the
error check so resources are always released.
Replace xzalloc() with xmalloc() and memset(0xff) for the dmabufs
array because zero is a valid fd number and would cause the cleanup
loop to close fd 0 (stdin) for unpopulated entries. Initializing
to KFD_INVALID_FD (0xffffffff) lets the exit block tell which fds
were actually opened. Guard amdgpu_device_deinitialize() behind a
dev_initialized flag to avoid calling it when initialization failed,
and close dmabuf_fd when drmPrimeFDToHandle() fails. To prevent
double-closing fds that restore_bo_contents_drm() already closed,
only run the close loop when the BO content restore was not reached.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Free the protobuf message and buffer when returning early from
amdgpu_plugin_dmabuf_restore() on failure to find a dmabuf fd.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
posix_memalign() returns an error code on failure and does not
guarantee setting the pointer to NULL. Check the return value
instead of the pointer. Since posix_memalign() does not set
errno, assign it explicitly so pr_perror() prints the correct
message. Negate the return code instead of hardcoding -ENOMEM
to preserve the actual error (e.g. EINVAL for bad alignment).
In amdgpu_plugin_drm_dump_file(), replace break with cleanup
and goto exit so per-BO resources are freed on failure.
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
When parse_threads() is called with a pre-allocated array (e.g., when
re-parsing threads), it must ensure that the number of threads found in
/proc does not exceed the size of the provided buffer. Add a check to
prevent out-of-bounds access if the thread count increases unexpectedly.
Signed-off-by: Andrei Vagin <avagin@google.com>
The exit status in the wait status word is 8 bits. Using a 0x7f mask was
incorrectly stripping the highest bit of the exit code. Use 0xff mask
instead to correctly preserve the full 8-bit exit status.
Signed-off-by: Andrei Vagin <avagin@google.com>
When writing numeric values to /proc files (like oom_score_adj, loginuid,
and pid_max), we were using hardcoded lengths or full buffer sizes in
write() calls. This could result in writing garbage characters from the
stack if the actual string was shorter than the specified length.
Use the length returned by snprintf() to ensure only the intended string
is written. Also, increase the buffer size for oom_score_adj to avoid
truncation.
Signed-off-by: Andrei Vagin <avagin@google.com>
mmap() returns MAP_FAILED ((void *)-1) on failure, not NULL. Checking
for NULL is incorrect and would miss actual failures, leading to
potential null pointer dereferences later.
Signed-off-by: Andrei Vagin <avagin@google.com>
siginfo_priv_nr was allocated in prepare_siginfo_priv() but never
freed. Add an xfree() call in sigreturn_restore() to clean it up.
Also, remove an incorrect attempt to free thread core entries inside the
main thread loop in sigreturn_restore(), as these are now properly
handled at the end of the function.
Signed-off-by: Andrei Vagin <avagin@google.com>
In open_cores(), we were not freeing already opened core entries if one
of them failed to open. Also, the cores array was not zero-initialized,
which could lead to freeing garbage pointers in the error path. Use
xzalloc() to ensure the array is zeroed.
In sigreturn_restore(), thread core entries were not being freed after
use, leading to a memory leak. Add a loop to free them.
Signed-off-by: Andrei Vagin <avagin@google.com>
The aarch64 Fedora Rawhide test has been moved to GitHub Actions
as part of the fedora-rawhide-test matrix build. Remove it from
the Cirrus CI configuration.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
Convert the fedora-rawhide-test job to a matrix build that runs
on both ubuntu-22.04 (x86_64) and ubuntu-24.04-arm (aarch64).
This replaces the aarch64 Fedora Rawhide build-only test
previously running on Cirrus CI with a full test run on GitHub
Actions.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
Remove leftover pidfile and criu_service.socket at the start of
the run target. If these files exist from a previous interrupted
run, the service fails to start and read.py hangs forever waiting
on the status fifo.
Acked-by: Andrei Vagin <avagin@gmail.com>
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Verify that the page server binds to the address given in
the RPC request when lazy_pages is enabled. Without the fix
in the previous commit, the address was silently ignored and
the page server would bind to 0.0.0.0 instead.
The test sends a PAGE_SERVER request with ps.address set to
127.0.0.1 and lazy_pages enabled, then checks /proc/net/tcp
to confirm the page server actually bound to that address.
Acked-by: Andrei Vagin <avagin@gmail.com>
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
The page server address from RPC requests was ignored when
lazy_pages was enabled. The code that copies the address into
opts.addr only ran inside the !opts.lazy_pages branch, so with
lazy pages the page server would bind to 0.0.0.0 instead of
the specified address.
This breaks live migration when the container runtime needs the
page server to listen on a specific address. The zeropod project
worked around this with a downstream patch that adds a raw
pointer assignment (opts.addr = req->ps->address), but that
causes a use-after-free when the protobuf message is freed.
Fix this by moving SET_CHAR_OPTS (which copies via strdup) and
the NULL fallback outside the !opts.lazy_pages block so they
apply unconditionally.
Reproducer (run as root, requires pycriu):
sleep 99999 &
criu dump -t $! -D /tmp/imgs --shell-job -v0
criu service --address /tmp/criu.sock -v0 &
python3 -c "
import os, socket
from pycriu import rpc_pb2 as rpc
s = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
s.connect('/tmp/criu.sock')
req = rpc.criu_req()
req.type = rpc.PAGE_SERVER
req.opts.images_dir_fd = os.open('/tmp/imgs', os.O_DIRECTORY)
req.opts.lazy_pages = True
req.opts.ps.port = 27
req.opts.ps.address = '127.0.0.1'
s.send(req.SerializeToString())
"
ss -tlnp sport = :27 # Bug: shows 0.0.0.0:27 instead of 127.0.0.1:27
Acked-by: Andrei Vagin <avagin@gmail.com>
Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
Add a test that verifies pipe ownership (uid/gid) is preserved across
checkpoint/restore. This test exercises the fix for issue #2984 where
accessing pipes via /proc/self/fd/ failed after restore because the
pipe ownership was changed to root.
The test:
- Creates a pipe and optionally changes ownership (if running as root)
- Records ownership before C/R
- Verifies ownership is preserved after restore
- Tests /proc/self/fd access works after restore
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
When CRIU runs as root and restores a pipe, the pipe inode is created
with root ownership. If the restored process runs as a non-root user
and tries to reopen the pipe via /proc/self/fd/<n>, the open() fails
with EACCES because the pipe inode is owned by root.
Fix this by saving the pipe's uid/gid during dump (from stat) and
restoring ownership with fchown() after creating the pipe. This
matches how memfds and TTYs already handle ownership restoration.
Fixes: #2984
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Cirrus CI reports "Failed to start an instance:
FAILED_PRECONDITION: Monthly compute limit exceeded!" making the
Vagrant Fedora Rawhide test unusable.
Replace the Cirrus CI Vagrant-based Fedora Rawhide test with a
Lima VM-based equivalent in GitHub Actions. This follows the same
pattern used by the runc project (lima-vm/lima-actions).
The new vagrant-fedora-rawhide-test job:
- Starts a Lima Fedora VM with KVM acceleration
- Installs the latest vanilla kernel
- Reboots the VM to activate the new kernel
- Runs the fedora-rawhide CI target inside a podman container
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
The --pids-limit=0 value does not disable the pids limit in
current podman versions. The podman documentation specifies
that -1 means unlimited. With --pids-limit=0, the thread-bomb
test fails on the second run (after restore) because each of
the 1024 threads tries to create a new thread, exceeding the
container's pids limit and causing pthread_create() to return
EAGAIN.
Change --pids-limit=0 to --pids-limit=-1 to properly disable
the pids cgroup limit.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
struct parasite_ctl, parasite_thread_ctl, and plain_regs_struct
all contain user_fpregs_struct_t (typedef for struct xsave_struct),
which is declared with __aligned(64). When these structs are
allocated on the heap with xmalloc/xzalloc (i.e. malloc/calloc),
the allocator only guarantees 16-byte alignment on x86_64.
The compiler, seeing the __aligned(64) attribute on the struct
type, may emit aligned memory instructions (e.g. movaps, vmovdqa)
for struct copies, assuming the memory is properly aligned. When
the heap pointer is not 64-byte aligned, these instructions trigger
a General Protection Fault (#GP).
This was observed as a crash in save_regs_plain() at infect.c:1314
(prs->regs = *r) on CentOS Stream 10 under QEMU/KVM, where the
compiler generated aligned vector instructions for the struct copy.
The crash did not reproduce on bare metal with a different compiler
version that happened to emit unaligned instructions.
Add xmemalign() wrapper around posix_memalign() to xmalloc.h and
use it for all three allocation sites:
- compel_prepare_noctx(): struct parasite_ctl
- compel_prepare_thread(): struct parasite_thread_ctl
- compel_prepare(): struct plain_regs_struct
Memory from posix_memalign() can be freed with free(), so no
changes to cleanup paths are needed.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Adrian Reber <areber@redhat.com>
The control message buffer was hardcoded to 1024 bytes, which is
insufficient for the kernel's maximum allowed file descriptor count.
SCM_RIGHTS can carry up to 253 file descriptors, requiring
CMSG_SPACE(253 * sizeof(int)) = 1032 bytes. Additionally, a single
packet can carry multiple SCM types (SCM_RIGHTS, SCM_CREDENTIALS,
SCM_PIDFD), pushing the worst case to ~1088 bytes.
Increase the buffer to 2048 bytes for safety.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Wire riscv64 CRIU core data into the Python coredump generator.
Set EM_RISCV in the ELF header, map GP registers for NT_PRSTATUS,
map FP registers for NT_FPREGSET, and enable the existing smoke test
on riscv64.
The layout follows the current CRIU riscv64 core format and the
Linux RISC-V user ABI.
Partially addresses #2433.
Signed-off-by: Shaurya Rane <ssrane_b23@ee.vjti.ac.in>
Add a ZDTM test for the timer_slack_ns checkpoint/restore feature.
The test sets a custom timer slack value (123456789 ns) before C/R
and verifies it is preserved after restore.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
During restore, set the per-thread timer_slack_ns value via
PR_SET_TIMERSLACK if the image contains it. This completes the
checkpoint/restore cycle for the timer slack property.
The restore is skipped for images that do not contain the field,
maintaining backward compatibility with older image formats.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
Collect the per-thread timer_slack_ns value from each thread via
PR_GET_TIMERSLACK in the parasite and store it in the thread core
image entry.
PR_GET_TIMERSLACK returns the slack value directly as a long, so a
separate long variable is used to avoid truncation when storing the
result.
Signed-off-by: Ahmed Elaidy <elaidya225@gmail.com>
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>