CVE-2026-32241
Description
Flannel is a network fabric for containers, designed for Kubernetes. The Flannel project includes an experimental Extension backend that allows users to easily prototype new backend types. In versions of Flannel prior to 0.28.2, this Extension backend is vulnerable to a command injection that allows an attacker who can set Kubernetes Node annotations to achieve root-level arbitrary command execution on every flannel node in the cluster. The Extension backend's SubnetAddCommand and SubnetRemoveCommand receive attacker-controlled data via stdin (from the flannel.alpha.coreos.com/backend-data Node annotation). The content of this annotation is unmarshalled and piped directly to a shell command without checks. Kubernetes clusters using Flannel with the Extension backend are affected by this vulnerability. Other backends such as vxlan and wireguard are unaffected. The vulnerability is fixed in version v0.28.2. As a workaround, use Flannel with another backend such as vxlan or wireguard.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/flannel-io/flannelGo | < 0.28.2 | 0.28.2 |
Affected products
1Patches
108bc9a4c990aDon't use shell invocations in extensions backend.
2 files changed · +38 −4
pkg/backend/extension/extension.go+33 −2 modified@@ -80,7 +80,8 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr data := []byte{} if len(n.preStartupCommand) > 0 { - cmd_output, err := runCmd([]string{}, "", "sh", "-c", n.preStartupCommand) + preArgs := strings.Fields(n.preStartupCommand) + cmd_output, err := runCmd([]string{}, "", preArgs[0], preArgs[1:]...) if err != nil { return nil, fmt.Errorf("failed to run command: %s Err: %v Output: %s", n.preStartupCommand, err, cmd_output) } else { @@ -121,13 +122,14 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr } if len(n.postStartupCommand) > 0 { + postArgs := strings.Fields(n.postStartupCommand) cmd_output, err := runCmd([]string{ fmt.Sprintf("NETWORK=%s", config.Network), fmt.Sprintf("SUBNET=%s", lease.Subnet), fmt.Sprintf("IPV6SUBNET=%s", lease.IPv6Subnet), fmt.Sprintf("PUBLIC_IP=%s", attrs.PublicIP), fmt.Sprintf("PUBLIC_IPV6=%s", attrs.PublicIPv6)}, - "", "sh", "-c", n.postStartupCommand) + "", postArgs[0], postArgs[1:]...) if err != nil { return nil, fmt.Errorf("failed to run command: %s Err: %v Output: %s", n.postStartupCommand, err, cmd_output) } else { @@ -140,8 +142,37 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr return n, nil } +// buildEnvMap merges os.Environ() with the provided env slice into a lookup map. +func buildEnvMap(env []string) map[string]string { + m := make(map[string]string) + for _, e := range os.Environ() { + k, v, _ := strings.Cut(e, "=") + m[k] = v + } + for _, e := range env { + k, v, _ := strings.Cut(e, "=") + m[k] = v + } + return m +} + +// expandVars expands $VAR / ${VAR} references in each string using the provided map. +// Because exec.Command is used (no shell), the expanded values are passed as literal +// arguments — shell metacharacters in variable values cannot cause injection. +func expandVars(envMap map[string]string, args []string) []string { + expanded := make([]string, len(args)) + for i, a := range args { + expanded[i] = os.Expand(a, func(key string) string { return envMap[key] }) + } + return expanded +} + // Run a cmd, returning a combined stdout and stderr. func runCmd(env []string, stdin string, name string, arg ...string) (string, error) { + envMap := buildEnvMap(env) + expanded := expandVars(envMap, append([]string{name}, arg...)) + name, arg = expanded[0], expanded[1:] + cmd := exec.Command(name, arg...) cmd.Env = append(os.Environ(), env...)
pkg/backend/extension/extension_network.go+5 −2 modified@@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "sync" "github.com/flannel-io/flannel/pkg/backend" @@ -88,11 +89,12 @@ func (n *network) handleSubnetEvents(batch []lease.Event) { } } + addArgs := strings.Fields(n.subnetAddCommand) cmd_output, err := runCmd([]string{ fmt.Sprintf("SUBNET=%s", evt.Lease.Subnet), fmt.Sprintf("PUBLIC_IP=%s", evt.Lease.Attrs.PublicIP)}, backendData, - "sh", "-c", n.subnetAddCommand) + addArgs[0], addArgs[1:]...) if err != nil { log.Errorf("failed to run command: %s Err: %v Output: %s", n.subnetAddCommand, err, cmd_output) @@ -118,11 +120,12 @@ func (n *network) handleSubnetEvents(batch []lease.Event) { continue } } + removeArgs := strings.Fields(n.subnetRemoveCommand) cmd_output, err := runCmd([]string{ fmt.Sprintf("SUBNET=%s", evt.Lease.Subnet), fmt.Sprintf("PUBLIC_IP=%s", evt.Lease.Attrs.PublicIP)}, backendData, - "sh", "-c", n.subnetRemoveCommand) + removeArgs[0], removeArgs[1:]...) if err != nil { log.Errorf("failed to run command: %s Err: %v Output: %s", n.subnetRemoveCommand, err, cmd_output)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-vchx-5pr6-ffx2ghsaADVISORY
- github.com/flannel-io/flannel/security/advisories/GHSA-vchx-5pr6-ffx2nvdMitigationVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-32241ghsaADVISORY
- github.com/flannel-io/flannel/commit/08bc9a4c990ae785d2fcb448f4991b58485cd26aghsaWEB
- github.com/flannel-io/flannel/releases/tag/v0.28.2nvdProductRelease NotesWEB
News mentions
0No linked articles in our index yet.