syzqemuctl
A command-line tool and Python API for managing QEMU disk images and virtual machines created through Syzkaller's `create-image.sh`.
Documentation
The repository also ships a portable syzqemuctl Agent Skill for Codex, Claude
Code, OpenCode, and other Agent Skills-compatible tools under
skills/syzqemuctl.
Features
- Easy VM creation and management
- Automated template image creation using syzkaller's create-image.sh
- Selectable Debian image distributions with Trixie as the new-install default
- SSH and file transfer support
- Command execution in VMs
- Guest-side syzkaller temporary artifact cleanup
- Screen session management for VM console access
See details in Usage section :)
Change Log
Each version without BUG tag is usable.
v0.1.0 ~ v0.1.10
- 0.1.0: 2025-01-16
- Initial release (BUG: entry_point is wrong)
- 0.1.1: 2025-01-16
- Update README.md (BUG: entry_point is wrong)
- 0.1.2: 2025-01-17
- Fix bug of entry point (CLI USABLE NOW!)
- 0.1.3: 2025-01-17
- Add badges
- 0.1.4: 2025-01-20
- Fix the inconsistencies of README and code (API USABLE NOW!)
- 0.1.5: 2025-01-21
- Complete vm.wait_until_ready and update README
- 0.1.6: 2025-01-21
- Update version info and try to solve the installation dependency problem
- 0.1.7: 2025-01-21
- Fix the installation dependency problem
- 0.1.8: 2025-01-22
- Add smart option --version and move some functions to utils.py
- 0.1.9: 2025-01-22
- Add safe_decode in execute in vm.py
- 0.1.10: 2025-01-22
- Use the kernel in last vm config to start vm by default
v0.2.0 ~ v0.2.9
- 0.2.0: 2025-04-25
- Add user friendly instruction for running image and update email
- 0.2.1: 2025-04-26
- Add documentation for copy dirs from local to vm
- 0.2.2: 2025-04-27
- Add restart for vm and update README
- 0.2.3: 2025-04-27
- Set default image size of image-template to 5GB and support --size for creating vm (BUG: size it doesn't work)
- 0.2.4: 2025-04-27
- Fix a missing file in creating vm with specified size and optimize printing
- 0.2.5: 2025-05-01
- Add security check for command injection
- 0.2.6: 2025-05-12
- Add blocking mode for init command
- 0.2.7: 2025-05-14
- Improve API usage
- 0.2.8: 2026-05-05
- Fix a vm booting bug caused by the cpu inconsistency by adding params in boot_script
- 0.2.9: 2026-05-11
- Suppress paramiko SSH noise and expose
set_paramiko_logging()for log control - Reduce
wait_until_ready()default polling interval to 3s and remove redundantis_ready()checks - Improve
stop()cleanup (screen session, stale pidfile) and fix return semantics - Fix bare
except:clauses instart()andutils.py, remove noisy prints fromis_ready()
- Suppress paramiko SSH noise and expose
v0.3.0 ~ v0.3.9
- 0.3.0: 2026-05-12
- Reduce default template size from 5120MB to 3072MB and add
--sizetoinit - Add template-size cache (
image-template-SIZE) for fastercreatewith custom sizes - Add
--forcetocreateto bypass cache and create from scratch - Add
is_image_ready()API and.image_readyflag for monitoring image creation - Distinguish image vs VM concepts in README and unify examples to
my-image
- Reduce default template size from 5120MB to 3072MB and add
- 0.3.1: 2026-05-13
- Add
--snapshotflag torunfor ephemeral VM sessions (changes discarded on shutdown) - Snapshot flag is not inherited from previous boots; specify it explicitly when needed
- Add
- 0.3.2: 2026-05-16
- Add
verboseparameter toVM,ImageManager, andglobal_conf.initialize() - API mode defaults to quiet (
verbose=False); informational prints are suppressed while errors are always preserved - CLI mode remains verbose (
verbose=True) to keep existing user experience
- Add
- 0.3.3: 2026-05-18
- Add
net.ifnames=0to kernel cmdline for stable guest NIC naming (eth0) - Use process substitution (
exec > >(tee)) in boot script so QEMU is a direct child of screen - Extend
start()polling to 30s and add failure cleanup (screen + pidfile) - Add explicit
banner_timeoutandauth_timeouttois_ready()(5s) andconnect()(10s)
- Add
- 0.3.4: 2026-05-25
- Expose public
timeoutsupport onVM.execute(),VM.copy_to_vm(), andVM.copy_from_vm() - Treat
timeoutas a total operation limit and keep existing behavior unchanged when omitted - Drain
stdoutandstderrconcurrently inVM.execute()to avoid dual-stream blocking on large output - Abort the current SSH/SCP connection on timeout so callers can recover with a fresh
connect() - Extend
VM.stop()withwaitandforceoptions, and addVM.cleanup_runtime()for strong runtime cleanup
- Expose public
- 0.3.5: 2026-08-16
- Add configurable guest kernel command lines through
kernel_argsandextra_kernel_argsin both API and CLI modes - Safely quote generated QEMU arguments and stop interpolating dynamic paths into image-creation shell commands
- Add path-specific screen names, per-image operation locking, exact QEMU/PID ownership checks, startup pre-cleanup, and deletion protection for running images
- Replace the
netstatport scan with socket binding and remove the duplicate SSH handshake fromconnect() - Expose timeout and strong-cleanup controls in the CLI, with non-zero exit status on operation failures
- Restore tracked unit tests and retain Python 3.8 compatibility
- Add configurable guest kernel command lines through
- 0.3.6: 2026-08-18
- Add the read-only
VM.runtime_diagnostics()API with structured screen, QEMU, pidfile, port, log, and runtime-cleanliness state - Bound external runtime checks by a caller-provided timeout and report unavailable state through nullable fields and
errors - Add
RuntimeDiagnostics.to_dict()andsummary()for machine-readable records and concise logs - Add
syzqemuctl diagnosewith optional--jsonand--no-check-portoutput controls
- Add the read-only
- 0.3.7: 2026-08-18
- Add official MkDocs Material documentation and Read the Docs build configuration
- Add a portable syzqemuctl Agent Skill with lifecycle and failure-handling guidance
- Validate documentation and Agent Skill structure in CI
- 0.3.8: 2026-08-19
- Adopt PEP 517 package builds while retaining setuptools and Python 3.8 compatibility
- Document Agent Skill installation for Codex, Claude Code, and OpenCode
- Add complete English and Simplified Chinese documentation with separate Read the Docs builds
- Validate wheel, source distribution, and both documentation languages in CI
- 0.3.9: 2026-09-18
- Add
VM.cleanup_guest_artifacts()for bounded cleanup of common syzkaller and reproducer temporary files inside a connected guest - Add
syzqemuctl cleanup-guest NAME --timeout 20for the same operation from the CLI - Keep guest filesystem cleanup separate from host-side VM stop, runtime cleanup, and diagnostics
- Add
v0.4.0 ~ progressing
- 0.4.0: 2026-09-18
- Add Debian distribution selection to image initialization and creation in both CLI and Python API modes
- Default new image homes to Trixie while preserving existing legacy Bullseye templates and images
- Adopt distribution-independent
disk.*files with validated image provenance manifests - Pin and checksum-verify Syzkaller's
create-image.shbefore execution - Isolate template caches by distribution and disk size
TODOs
- Merge global_conf into ImageManager
Installation
pip install syzqemuctl
Requirements
python3.8+ qemu screen ssh
Configuration
The configuration file is stored in ~/.config/syzqemuctl/settings.json. It contains:
- Images home directory path
- Default VM settings
Concepts
- Image: A QEMU disk image created by
create-image.sh. New images usedisk.img; v0.3.x Bullseye images retainbullseye.img. Images are stored as directories underIMAGES_HOME. - VM: A running QEMU virtual machine booted from an image with a specified kernel. A VM shares the same name as its underlying image directory.
Usage
⭐ As a command-line tool (CLI)
You can check the usage of syzqemuctl or syzqemuctl CMD by adding --help. Here are some common uses:
- Initialize syzqemuctl:
syzqemuctl init --images-home /path/to/images
New image homes default to Debian Trixie. Select another release explicitly for historical-kernel compatibility:
syzqemuctl init --images-home /path/to/images \
--distribution bullseye --wait
- Create a new disk image:
syzqemuctl create my-image [--size 3072] [--distribution bullseye]
Omitting --distribution inherits the initialized default template. New
caches are separated by distribution and size.
- Run a VM from the image:
syzqemuctl run my-image --kernel /path/to/kernel
Run with snapshot mode (all disk changes discarded on shutdown):
syzqemuctl run my-image --kernel /path/to/kernel --snapshot
Append case-specific arguments to the saved command line, or to the default command line on first boot:
syzqemuctl run my-image --kernel /path/to/kernel \
--extra-kernel-args "systemd.unified_cgroup_hierarchy=0"
Use --kernel-args "..." instead to replace the complete guest kernel
command line. Explicit kernel arguments are stored in boot.sh and reused
by later starts unless new arguments are supplied.
- Check image/VM status:
syzqemuctl status my-image
- Copy files/dir to/from VM:
syzqemuctl cp --timeout 600 local_file my-image:/remote/path # Copy to VM
syzqemuctl cp --timeout 600 my-image:/remote/file local_path # Copy from VM
syzqemuctl cp local_dir my-image:/remote/ # Copy local_dir to VM
syzqemuctl cp local_dir/ my-image:/remote/ # Copy local_dir/* to VM
- Execute commands in VM:
syzqemuctl exec --timeout 30 my-image "uname -a" # You'd better wrap the command with double quotes
- Remove common temporary artifacts left by an earlier syzkaller reproducer:
syzqemuctl cleanup-guest my-image --timeout 20
This command requires a running, SSH-ready VM. It does not stop the VM or clean host-side runtime resources.
- Stop the VM:
syzqemuctl stop my-image
syzqemuctl stop my-image --wait --force --timeout 20
- Restart the VM:
syzqemuctl restart my-image
- Diagnose VM runtime resources without changing them:
syzqemuctl diagnose my-image
syzqemuctl diagnose my-image --timeout 5 --json
- List all images:
syzqemuctl list
- Delete the image:
syzqemuctl delete my-image
A running image must be stopped before it can be deleted.
⭐ As a Python package (API)
from syzqemuctl import global_conf, ImageManager, VM
images_home = "/path/to/images_home"
# API defaults to quiet (verbose=False); pass verbose=True to see informational prints
global_conf.initialize(images_home, force=False)
manager = ImageManager(images_home)
manager.initialize(force=False, distribution="trixie")
manager.create("my-image")
# Select an older userspace for a historical kernel when needed:
# manager.create("old-image", distribution="bullseye", size=4096)
# Or just direct specify a created image and run a VM from it
# Use verbose=True if you want to see boot script and screen session tips
vm = VM("/path/to/images_home/my-image")
vm.start(
kernel="/path/to/kernel",
extra_kernel_args="systemd.unified_cgroup_hierarchy=0",
)
# Use kernel_args="..." to replace the complete guest kernel command line.
# start() cleans stale runtime belonging to this image before launching QEMU.
# Wait several minutes for the VM to be ready, or you can check by:
if vm.is_ready():
pass
# Or use this API to wait:
if vm.wait_until_ready(timeout=180):
pass
# You need to use this context manager to auto-connect/disconnect
with vm:
# Run after the previous workload has stopped and before uploading a new PoC.
vm.cleanup_guest_artifacts(timeout=20)
vm.copy_to_vm("/path/to/local/file", "/path/to/vm/remote/file", timeout=600)
stdout, stderr = vm.execute("uname -a", timeout=30)
print(f"stdout: {stdout}\nstderr: {stderr}")
# timeout is optional and defaults to None.
# It limits the total duration of a single execute/copy operation.
# After a timeout, reconnect before issuing the next SSH/SCP request.
# Use wait=True and force=True when you need runtime cleanup to fully converge.
vm.stop(wait=True, force=True, timeout=20)
vm.cleanup_runtime(timeout=20)
# force=True is useful after a failed start.
# wait=True is useful before reusing an image.
# Collect a read-only runtime snapshot after start or cleanup failures.
diagnostics = vm.runtime_diagnostics(timeout=5, check_port=True)
print(diagnostics.summary())
record = diagnostics.to_dict() # JSON/CSV-friendly structured fields
License
Apache-2.0
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Metadata
Release files for syzqemuctl 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| syzqemuctl-0.4.0.tar.gz | 93.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| syzqemuctl-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 130.1 kB
Release files / syzqemuctl-0.4.0.tar.gz
| Download URL | syzqemuctl-0.4.0.tar.gz |
|---|---|
| Size | 93.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3a9f24994ce649ef9b3f62725bae89b3ffcbba59886abd1542ebc2a4aef1e782
|
|
BLAKE2b-256 checksum How to use checksums |
c06cc9113fed5e747b229150253688a6c05d6142a96cf99a7d1c60f9049b777a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.8
|
Release files / syzqemuctl-0.4.0-py3-none-any.whl
| Download URL | syzqemuctl-0.4.0-py3-none-any.whl |
|---|---|
| Size | 36.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
da6336194ef23022d68614c7edec8ec90719754de2bed1592e0522c63b508e30
|
|
BLAKE2b-256 checksum How to use checksums |
9bdc3ddc8abe5e4cd276e2e2faee37b3f160781569854a1feaca0f43a4d7983a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.8
|