Bug 2419661 - incus-agent must be statically linked for VM exec to work
Summary: incus-agent must be statically linked for VM exec to work
Keywords:
Status: MODIFIED
Alias: None
Product: Fedora
Classification: Fedora
Component: incus
Version: 43
Hardware: x86_64
OS: Linux
unspecified
high
Target Milestone: ---
Assignee: Neal Gompa
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2025-12-06 10:44 UTC by julien.ravaiau+redhat
Modified: 2026-01-29 18:28 UTC (History)
8 users (show)

Fixed In Version: incus-6.18-2.fc44 incus-6.19.1-1.fc43 incus-6.19.1-1.fc42
Clone Of:
Environment:
Last Closed: 2025-12-20 00:54:46 UTC
Type: ---
Embargoed:


Attachments (Terms of Use)

Description julien.ravaiau+redhat 2025-12-06 10:44:47 UTC
The incus-agent binary provided by the Fedora package is dynamically linked, which causes "websocket: bad handshake" errors when using `incus exec` on virtual machines.

The incus-agent is injected into VMs at boot time via a 9p/virtiofs mount and must be statically linked to work correctly regardless of the guest OS libraries.

$ file /usr/bin/incus-agent
/usr/bin/incus-agent: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2

Upstream provides static binaries that work correctly:
https://github.com/lxc/incus/releases (bin.linux.incus-agent.x86_64)

Reproducible: Always

Steps to Reproduce:
1. Install incus and incus-agent packages
2. Create a VM: incus launch images:ubuntu/24.04 testvm --vm
3. Wait for the VM to boot
4. Execute a command: incus exec testvm id
Actual Results:
Error: websocket: bad handshake

Expected Results:
Command executes successfully inside the VM

Additional Information:
Workaround: Replace with static binary from upstream:
curl -L -o /usr/bin/incus-agent https://github.com/lxc/incus/releases/latest/download/bin.linux.incus-agent.x86_64

References:
- Debian fixed this in incus 6.0.5-2: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1114918
- Forum discussion: https://discuss.linuxcontainers.org/t/incus-exec-failing-for-vms-with-a-websocket-bad-handshake/24865

Comment 1 julien.ravaiau+redhat 2025-12-06 11:02:10 UTC
Same issue confirmed on Fedora 43:

[root@fedora ~]# grep PRETTY /etc/os-release 
PRETTY_NAME="Fedora Linux 43"

[root@fedora ~]# file /usr/bin/incus-agent 
/usr/bin/incus-agent: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=957004a18c107c93a8e7252d0c563c03d4131721, for GNU/Linux 3.2.0, stripped

The binary is still dynamically linked, causing the same "websocket: bad handshake" error on VMs.

Comment 2 Fedora Update System 2025-12-06 12:50:19 UTC
FEDORA-2025-77328eb306 (incus-6.18-2.fc44) has been submitted as an update to Fedora 44.
https://bodhi.fedoraproject.org/updates/FEDORA-2025-77328eb306

Comment 3 Fedora Update System 2025-12-06 12:53:24 UTC
FEDORA-2025-77328eb306 (incus-6.18-2.fc44) has been pushed to the Fedora 44 stable repository.
If problem still persists, please make note of it in this bug report.

Comment 4 Fedora Update System 2025-12-06 12:53:36 UTC
FEDORA-2025-ba21fcf444 (incus-6.18-2.fc42) has been submitted as an update to Fedora 42.
https://bodhi.fedoraproject.org/updates/FEDORA-2025-ba21fcf444

Comment 5 Fedora Update System 2025-12-06 12:53:39 UTC
FEDORA-2025-0ad53ef7cd (incus-6.18-2.fc43) has been submitted as an update to Fedora 43.
https://bodhi.fedoraproject.org/updates/FEDORA-2025-0ad53ef7cd

Comment 6 Fedora Update System 2025-12-07 01:10:40 UTC
FEDORA-2025-0ad53ef7cd has been pushed to the Fedora 43 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-0ad53ef7cd`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-0ad53ef7cd

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 7 Fedora Update System 2025-12-07 01:45:57 UTC
FEDORA-2025-ba21fcf444 has been pushed to the Fedora 42 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-ba21fcf444`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-ba21fcf444

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 8 Fedora Update System 2025-12-08 00:51:37 UTC
FEDORA-2025-ba21fcf444 has been pushed to the Fedora 42 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-ba21fcf444`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-ba21fcf444

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 9 Fedora Update System 2025-12-08 01:06:58 UTC
FEDORA-2025-0ad53ef7cd has been pushed to the Fedora 43 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-0ad53ef7cd`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-0ad53ef7cd

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 10 Fedora Update System 2025-12-12 02:23:14 UTC
FEDORA-2025-9cbe00a763 has been pushed to the Fedora 42 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-9cbe00a763`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-9cbe00a763

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 11 Fedora Update System 2025-12-12 02:39:49 UTC
FEDORA-2025-2c3612f5ce has been pushed to the Fedora 43 testing repository.
Soon you'll be able to install the update with the following command:
`sudo dnf upgrade --enablerepo=updates-testing --refresh --advisory=FEDORA-2025-2c3612f5ce`
You can provide feedback for this update here: https://bodhi.fedoraproject.org/updates/FEDORA-2025-2c3612f5ce

See also https://fedoraproject.org/wiki/QA:Updates_Testing for more information on how to test updates.

Comment 12 julien.ravaiau+redhat 2025-12-15 23:06:50 UTC
Hello,

The issue is not fully fixed,
My debug attempt here : https://bodhi.fedoraproject.org/updates/FEDORA-2025-ba21fcf444#comment-4470993

Regards,
Julien

Comment 13 Fedora Update System 2025-12-20 00:54:46 UTC
FEDORA-2025-2c3612f5ce (incus-6.19.1-1.fc43) has been pushed to the Fedora 43 stable repository.
If problem still persists, please make note of it in this bug report.

Comment 14 Fedora Update System 2025-12-20 01:21:30 UTC
FEDORA-2025-9cbe00a763 (incus-6.19.1-1.fc42) has been pushed to the Fedora 42 stable repository.
If problem still persists, please make note of it in this bug report.

Comment 15 julien.ravaiau+redhat 2025-12-20 10:37:18 UTC
The problem still persist in 6.19

Comment 16 alfonsosanchez12 2026-01-14 04:30:14 UTC
Can confirm the problem persists. 
```
~ ➜  incus version
Client version: 6.19.1
Server version: 6.19.1
```

```
~ ➜  incus exec debian-vm -- bash
Error: websocket: bad handshake
```

Comment 17 julien.ravaiau+redhat 2026-01-29 01:24:49 UTC
The incus-agent binary built via the spec's custom %gobuild_static macro fails at runtime with "websocket: bad handshake" when communicating with the incus daemon inside VMs.

A manually built incus-agent using the same Go version and source works correctly.

Root cause:

The %gobuild_static macro uses GO111MODULE=off (via %gomodulesmode), which puts Go in legacy GOPATH mode.
In this mode, vendored dependency resolution relies on GOPATH directory structure and symlinks set up by %goprep, rather than on go.mod and vendor/modules.txt.

Testing with 3 build variants confirms that the discriminating factor is GO111MODULE=off vs GO111MODULE=on -mod=vendor:

| Build                          | GO111MODULE   | -mod=vendor | Result |
|--------------------------------|---------------|-------------|--------|
| Original %gobuild_static       | off (GOPATH)  | no          | BROKEN |
| Same flags, module mode        | on            | yes         | works  |
| Minimal flags, GOPATH mode     | off           | no          | BROKEN |
| Minimal flags, module mode     | on            | yes         | works  |

The GOPATH-mode vendor resolution (through the _build/src/github.com/lxc/incus symlink created by %goprep) produces a binary where the websocket handshake fails.
Module-mode resolution (-mod=vendor reading vendor/modules.txt) produces a working binary.

Note: /usr/share/gocode in the mock chroot is empty (only skeleton directories, no .go files), so this is not a package shadowing issue.
The problem is probably in how GOPATH-mode resolves the vendor directory through the %goprep symlink structure.

Fix:

The %gobuild_static custom macro in incus.spec should use GO111MODULE=on -mod=vendor instead of %{?gomodulesmode} (GO111MODULE=off).
This is the correct mode for a project with go.mod and a vendor/ directory.
The change only affects the custom static build macro; all other binaries continue to use the standard %gobuild macro.

❯ git diff incus.spec
diff --git a/incus.spec b/incus.spec
index 1d850c9..5ae4611 100644
--- a/incus.spec
+++ b/incus.spec
@@ -19,13 +19,14 @@ Version:        6.19.1

 # Set build macro for static builds
+# Uses GO111MODULE=on -mod=vendor instead of %%gomodulesmode (GO111MODULE=off)
+# to ensure vendored dependencies are correctly resolved (rhbz#XXXXXX)
 %define gocompilerflags_static -compiler gc
-%define gobuild_baseflags_static %{gocompilerflags_static} -tags="rpm_crashtraceback ${GO_BUILDTAGS-${BUILDTAGS-}}" -a -v
+%define gobuild_baseflags_static %{gocompilerflags_static} -mod=vendor -tags="rpm_crashtraceback ${GO_BUILDTAGS-${BUILDTAGS-}}" -a -v
 %define gobuild_ldflags_static ${GO_LDFLAGS-${LDFLAGS-}} %{?currentgoldflags} -B 0x$(echo "%{name}-%{version}-%{release}-${SOURCE_DATE_EPOCH:-}" | sha1sum | cut -d ' ' -f1) -compressdwarf=false
 %define gobuildflags_static() %{expand:%{gobuild_baseflags_static} -ldflags "%{gobuild_ldflags_static}"}
 %define gobuild_static(-) %{expand:
-  %{?gobuilddir:GOPATH="%{gobuilddir}:${GOPATH:+${GOPATH}:}%{?gopath}"} %{?gomodulesmode} \\
-  CGO_ENABLED=0 go build %{gobuildflags_static} %{?**};

Rebuild the rpm :

❯ mock -r fedora-43-x86_64 (...)
❯ sudo dnf reinstall mock-results-dir/incus-agent-6.19.1-1.fc43.x86_64.rpm
❯ incus restart test; sleep 20
❯ incus exec test id
uid=0(root) gid=0(root) groups=0(root)

This may also indicate a broader issue with %gomodulesmode (GO111MODULE=off) when used with projects that have vendored dependencies and go.mod — the GOPATH-mode vendor resolution through %goprep symlinks may not be fully equivalent to module-mode -mod=vendor resolution.

Julien

Comment 18 julien.ravaiau+redhat 2026-01-29 01:26:32 UTC
Incomplete git diff in the previous message : 

❯ git diff incus.spec
diff --git a/incus.spec b/incus.spec
index 1d850c9..5ae4611 100644
--- a/incus.spec
+++ b/incus.spec
@@ -19,13 +19,14 @@ Version:        6.19.1
 
 
 # Set build macro for static builds
+# Uses GO111MODULE=on -mod=vendor instead of %%gomodulesmode (GO111MODULE=off)
+# to ensure vendored dependencies are correctly resolved (rhbz#XXXXXX)
 %define gocompilerflags_static -compiler gc
-%define gobuild_baseflags_static %{gocompilerflags_static} -tags="rpm_crashtraceback ${GO_BUILDTAGS-${BUILDTAGS-}}" -a -v
+%define gobuild_baseflags_static %{gocompilerflags_static} -mod=vendor -tags="rpm_crashtraceback ${GO_BUILDTAGS-${BUILDTAGS-}}" -a -v
 %define gobuild_ldflags_static ${GO_LDFLAGS-${LDFLAGS-}} %{?currentgoldflags} -B 0x$(echo "%{name}-%{version}-%{release}-${SOURCE_DATE_EPOCH:-}" | sha1sum | cut -d ' ' -f1) -compressdwarf=false
 %define gobuildflags_static() %{expand:%{gobuild_baseflags_static} -ldflags "%{gobuild_ldflags_static}"}
 %define gobuild_static(-) %{expand:
-  %{?gobuilddir:GOPATH="%{gobuilddir}:${GOPATH:+${GOPATH}:}%{?gopath}"} %{?gomodulesmode} \\
-  CGO_ENABLED=0 go build %{gobuildflags_static} %{?**};
+  GO111MODULE=on CGO_ENABLED=0 go build %{gobuildflags_static} %{?**};
 }
 
 
@@ -360,8 +361,11 @@ for cmd in incus fuidshift incus-benchmark lxc-to-incus lxd-to-incus; do
 done
 
 # Build incus-migrate and incus-agent statically (cf. rhbz#2419661)
-BUILDTAGS="netgo" %gobuild_static -o %{gobuilddir}/bin/incus-migrate %{goipath}/cmd/incus-migrate
-BUILDTAGS="agent netgo" %gobuild_static -o %{gobuilddir}/bin/incus-agent %{goipath}/cmd/incus-agent
+# Uses GO111MODULE=on -mod=vendor, so paths must be relative to module root
+pushd %{currentgosourcedir}
+BUILDTAGS="netgo" %gobuild_static -o %{gobuilddir}/bin/incus-migrate ./cmd/incus-migrate
+BUILDTAGS="agent netgo" %gobuild_static -o %{gobuilddir}/bin/incus-agent ./cmd/incus-agent
+popd
 
 # build shell completions
 mkdir %{gobuilddir}/completions


Note You need to log in before you can comment on or make changes to this bug.