Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save li-zhixin/5a074546edbc383244bce05c60343936 to your computer and use it in GitHub Desktop.

Select an option

Save li-zhixin/5a074546edbc383244bce05c60343936 to your computer and use it in GitHub Desktop.
Docker Desktop + CRIU checkpoint on WSL2

Docker Desktop + CRIU Checkpoint on WSL2

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
  • runc 1.3.5
  • WSL repack environment: Ubuntu 25.04

What needs fixing

Two things block CRIU on this Docker Desktop build:

  1. /initd installs a seccomp filter that breaks CRIU feature probing.
  2. dockerd needs a working criu plus private runtime libraries inside its own mount namespace.

Expected end state

After patching:

  • docker info --format '{{json .ExperimentalBuild}}' returns true
  • dockerd status shows:
NoNewPrivs:      0
Seccomp:         0
Seccomp_filters: 0
  • inside dockerd's mount namespace:
/usr/sbin/criu -V

returns:

Version: 4.0
  • docker checkpoint create ... works
  • docker start --checkpoint ... works

Important implementation detail

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.

/initd patch

For Docker Desktop 4.78.0, patch these offsets in /initd:

  • 0x21cb5f9
  • 0x21cb645

Replace 6 bytes at both locations with:

31 c0 90 90 90 90

How to locate the offsets on other versions

Do not assume the same offsets across Docker Desktop releases. Re-locate them from the target ISO's /initd.

Principle

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:

  1. try the modern seccomp install path first
  2. 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.

Method

  1. Extract /initd from the target docker-desktop.iso
  2. Disassemble the nearby code
  3. Find the block that:
    • builds seccomp install flags
    • attempts seccomp filter installation
    • falls back to prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
  4. Patch the 6-byte call instruction at both sites with:
31 c0 90 90 90 90

Example workflow

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" | less

What to look for

You 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 call is the primary seccomp filter install path
  • the second call is the fallback prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)

The fallback is the easiest anchor because:

  • 0x16 is PR_SET_SECCOMP
  • 0x2 is SECCOMP_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

Narrowing the search

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:

  1. the block prepares seccomp-related flags in edx
  2. one call is the primary install path
  3. one nearby fallback call uses prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)
  4. both calls belong to the same control-flow region

CRIU layout inside the ISO

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.

Build script

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"

Install patched 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.iso
    • docker-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'

Enable Docker experimental mode

Checkpoint still requires experimental mode on this build.

Verify:

docker info --format '{{json .ExperimentalBuild}}'

Expected:

true

Verify the patch

1. Confirm Docker Desktop is using the patched ISO

Inside docker-desktop:

mount | grep docker-desktop-root

You should see the patched ISO mounted under something like:

/mnt/docker-desktop-disk/isocache/entries/docker-desktop.iso/<sha256>

2. Confirm seccomp is disabled for dockerd

Inside docker-desktop:

ps -ef | grep '[d]ockerd --config-file'

Take the PID, then:

grep -E 'NoNewPrivs|Seccomp|Seccomp_filters' /proc/<dockerd-pid>/status

Expected:

NoNewPrivs:      0
Seccomp:         0
Seccomp_filters: 0

3. Confirm criu from dockerd's mount namespace

Inside docker-desktop:

nsenter -t <dockerd-pid> -m -- /bin/sh -lc '/usr/sbin/criu -V'

Expected:

Version: 4.0

Smoke test

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-test

Expected:

  • docker checkpoint create succeeds
  • restore succeeds
  • container is running after restore

Known limits

  • Prefer --network host for 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.

Rollback

$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'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment