This is a recipe for enabling docker checkpoint create on Docker Desktop by patching docker-desktop.iso.
Verified on:
- Docker Desktop
4.78.0 (229452) - Docker Engine
29.5.3 runc1.3.5- WSL repack environment: Ubuntu
25.04
Two things block CRIU on this Docker Desktop build:
/initdinstalls a seccomp filter that breaks CRIU feature probing.dockerdneeds a workingcriuplus private runtime libraries inside its own mount namespace.
After patching:
docker info --format '{{json .ExperimentalBuild}}'returnstruedockerdstatus shows:
NoNewPrivs: 0
Seccomp: 0
Seccomp_filters: 0
- inside
dockerd's mount namespace:
/usr/sbin/criu -Vreturns:
Version: 4.0
docker checkpoint create ...worksdocker start --checkpoint ...works
On this Docker Desktop build, the distro root / is not the effective root used by dockerd.
The engine runs from an overlay rooted at:
/tmp/docker-desktop-root-ro/tmp/docker-desktop-root
So verification must be done in dockerd's mount namespace, not only from the distro shell.
For Docker Desktop 4.78.0, patch these offsets in /initd:
0x21cb5f90x21cb645
Replace 6 bytes at both locations with:
31 c0 90 90 90 90
Do not assume the same offsets across Docker Desktop releases.
Re-locate them from the target ISO's /initd.
You are not looking for arbitrary constants. You are looking for the code path that installs the inherited seccomp BPF filter.
That logic has two stages:
- try the modern seccomp install path first
- if needed, fall back to
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
The patch works by turning both install calls into a no-op success return.
- Extract
/initdfrom the targetdocker-desktop.iso - Disassemble the nearby code
- Find the block that:
- builds seccomp install flags
- attempts seccomp filter installation
- falls back to
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
- Patch the 6-byte
callinstruction at both sites with:
31 c0 90 90 90 90
Run in Ubuntu WSL:
BASE_ISO='/mnt/c/Program Files/Docker/Docker/resources/docker-desktop.iso'
WORK='/tmp/dd-find-offsets'
rm -rf "$WORK"
mkdir -p "$WORK"
xorriso -osirrox on -indev "$BASE_ISO" -extract /initd "$WORK/initd" >/dev/null 2>&1
objdump -d "$WORK/initd" | lessYou are looking for a block shaped like this:
... optional flag setup ...
or $0x20,%edx
or $0x2,%edx
or $0x4,%edx
... first install attempt ...
...
... fallback install attempt ...
mov $0x2,%esi
mov $0x16,%edi
xor %eax,%eax
... call ...Semantically:
- the first
callis the primary seccomp filter install path - the second
callis the fallbackprctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
The fallback is the easiest anchor because:
0x16isPR_SET_SECCOMP0x2isSECCOMP_MODE_FILTER
On 4.78.0, the matching block looked like:
21cb5d3: 83 ca 20 or $0x20,%edx
21cb5dc: 83 ca 02 or $0x2,%edx
21cb5e5: 83 ca 04 or $0x4,%edx
21cb5ef: 4c 89 e9 mov %r13,%rcx
21cb5f2: be 01 00 00 00 mov $0x1,%esi
21cb5f7: 31 c0 xor %eax,%eax
21cb5f9: 67 e8 27 ad 00 00 addr32 call ...
...
21cb636: 4c 89 ea mov %r13,%rdx
21cb639: be 02 00 00 00 mov $0x2,%esi
21cb63e: bf 16 00 00 00 mov $0x16,%edi
21cb643: 31 c0 xor %eax,%eax
21cb645: 67 e8 ba 8e 00 00 addr32 call ...The patch offsets are the addresses of those two call instructions:
- first call offset
- second call offset
If the full disassembly is too large, search for the fallback first:
objdump -d "$WORK/initd" | grep -n -A12 -B8 'bf 16 00 00 00'Then inspect upward and find the nearby primary install call in the same block.
You can also search for the flag-building pattern:
objdump -d "$WORK/initd" | grep -n -A16 -B8 '83 ca 20'
objdump -d "$WORK/initd" | grep -n -A16 -B8 '83 ca 02'
objdump -d "$WORK/initd" | grep -n -A16 -B8 '83 ca 04'Then confirm all of the following are true before patching:
- the block prepares seccomp-related flags in
edx - one call is the primary install path
- one nearby fallback call uses
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...) - both calls belong to the same control-flow region
Install:
/usr/sbin/criu
/usr/local/libexec/criu/criu
/usr/local/libexec/criu/lib/*
Wrapper script:
#!/bin/sh
exec /usr/local/libexec/criu/lib/ld-linux-x86-64.so.2 \
--library-path /usr/local/libexec/criu/lib \
/usr/local/libexec/criu/criu "$@"Use real .so files, not only symlinks.
Run in Ubuntu WSL. Adjust the two Windows paths first.
set -euo pipefail
BASE_ISO='/mnt/c/Program Files/Docker/Docker/resources/docker-desktop.iso'
OUT_ISO='/mnt/c/Program Files/Docker/Docker/resources/docker-desktop.iso.criu-patched.iso'
WORK='/tmp/ddcriu'
rm -rf "$WORK"
mkdir -p "$WORK/iso/usr/local/libexec/criu/lib" "$WORK/iso/usr/sbin"
xorriso -osirrox on -indev "$BASE_ISO" -extract /initd "$WORK/iso/initd" >/dev/null 2>&1
python3 - <<'PY'
from pathlib import Path
p = Path('/tmp/ddcriu/iso/initd')
b = bytearray(p.read_bytes())
for off in (0x21cb5f9, 0x21cb645):
b[off:off+6] = bytes.fromhex('31 c0 90 90 90 90')
p.write_bytes(b)
print('patched initd')
PY
cp -L /usr/sbin/criu "$WORK/iso/usr/local/libexec/criu/criu"
ldd /usr/sbin/criu | awk '/=> \\/|^\\// {for(i=1;i<=NF;i++) if ($i ~ /^\\//) print $i}' | sort -u > "$WORK/libs.txt"
echo /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 >> "$WORK/libs.txt"
while read -r lib; do
[ -n "$lib" ] || continue
cp -L "$lib" "$WORK/iso/usr/local/libexec/criu/lib/"
done < "$WORK/libs.txt"
cat > "$WORK/iso/usr/sbin/criu" <<'EOF'
#!/bin/sh
exec /usr/local/libexec/criu/lib/ld-linux-x86-64.so.2 \
--library-path /usr/local/libexec/criu/lib \
/usr/local/libexec/criu/criu "$@"
EOF
chmod 0755 "$WORK/iso/usr/sbin/criu"
rm -f "$OUT_ISO"
xorriso -indev "$BASE_ISO" \
-outdev "$OUT_ISO" \
-boot_image any replay \
-map "$WORK/iso/initd" /initd \
-map "$WORK/iso/usr/sbin/criu" /usr/sbin/criu \
-map "$WORK/iso/usr/local/libexec/criu/criu" /usr/local/libexec/criu/criu \
-map "$WORK/iso/usr/local/libexec/criu/lib" /usr/local/libexec/criu/lib \
-commit -end || true
sha256sum "$OUT_ISO"Run in PowerShell. Adjust paths first.
Notes:
- The patched ISO and backup files do not need separate directories.
- They may live in the same directory as
docker-desktop.iso. - The only requirement is: use different filenames.
- Only the final install step should overwrite:
docker-desktop.isodocker-desktop.iso.sha256
$DockerResources = 'C:\Program Files\Docker\Docker\resources'
$PatchedIso = 'C:\Program Files\Docker\Docker\resources\docker-desktop.iso.criu-patched.iso'
$BackupDir = 'C:\Program Files\Docker\Docker\resources'
New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
Copy-Item "$DockerResources\docker-desktop.iso" "$BackupDir\docker-desktop.iso.bak" -Force
Copy-Item "$DockerResources\docker-desktop.iso.sha256" "$BackupDir\docker-desktop.iso.sha256.bak" -Force
$Hash = (Get-FileHash $PatchedIso -Algorithm SHA256).Hash.ToLower()
$Hash | Set-Content -NoNewline "$DockerResources\docker-desktop.iso.sha256"
Copy-Item $PatchedIso "$DockerResources\docker-desktop.iso" -Force
Get-Process 'Docker Desktop','com.docker.backend','docker-desktop','vpnkit','com.docker.proxy','com.docker.build' `
-ErrorAction SilentlyContinue |
Stop-Process -Force -ErrorAction SilentlyContinue
wsl --shutdown
Start-Sleep -Seconds 3
Start-Process -FilePath 'C:\Program Files\Docker\Docker\Docker Desktop.exe'Checkpoint still requires experimental mode on this build.
Verify:
docker info --format '{{json .ExperimentalBuild}}'Expected:
true
Inside docker-desktop:
mount | grep docker-desktop-rootYou should see the patched ISO mounted under something like:
/mnt/docker-desktop-disk/isocache/entries/docker-desktop.iso/<sha256>
Inside docker-desktop:
ps -ef | grep '[d]ockerd --config-file'Take the PID, then:
grep -E 'NoNewPrivs|Seccomp|Seccomp_filters' /proc/<dockerd-pid>/statusExpected:
NoNewPrivs: 0
Seccomp: 0
Seccomp_filters: 0
Inside docker-desktop:
nsenter -t <dockerd-pid> -m -- /bin/sh -lc '/usr/sbin/criu -V'Expected:
Version: 4.0
docker run -d --name criu-test --network host busybox sh -c 'while true; do sleep 1; done'
docker checkpoint create criu-test cp1
docker stop criu-test
docker start --checkpoint cp1 criu-test
docker ps --filter name=criu-testExpected:
docker checkpoint createsucceeds- restore succeeds
- container is running after restore
- Prefer
--network hostfor restore tests. - Bridge-network restore may still fail with:
bind-mount /proc/0/ns/net -> /var/run/docker/netns/...: no such file or directory
- Restore may intermittently race in containerd and require a retry.
$DockerResources = 'C:\Program Files\Docker\Docker\resources'
$BackupDir = 'C:\Program Files\Docker\Docker\resources'
Get-Process 'Docker Desktop','com.docker.backend','docker-desktop','vpnkit','com.docker.proxy','com.docker.build' `
-ErrorAction SilentlyContinue |
Stop-Process -Force -ErrorAction SilentlyContinue
wsl --shutdown
Copy-Item "$BackupDir\docker-desktop.iso.bak" "$DockerResources\docker-desktop.iso" -Force
Copy-Item "$BackupDir\docker-desktop.iso.sha256.bak" "$DockerResources\docker-desktop.iso.sha256" -Force
Start-Process -FilePath 'C:\Program Files\Docker\Docker\Docker Desktop.exe'