From 3f70abdc6fa138f9f0d58b5aa24c4beadb6eb32c Mon Sep 17 00:00:00 2001 From: ferrumclaudepilgrim Date: Sun, 3 May 2026 20:14:30 -0500 Subject: [PATCH] cmd/tailscaled, version/distro: default to userspace-networking on Crostini cros-garcon NULL-derefs on cold-boot netlink enumeration when tailscale0 is present, preventing the Crostini container and ChromeOS Terminal from starting cleanly. This is an upstream ChromiumOS bug in cros-garcon; tailscaled can work around it by defaulting to userspace-networking mode on Crostini. Tailscale SSH continues to work via tailscaled's netstack. Users can override with --tun=tailscale0 on ChromeOS builds where cros-garcon is fixed. Crostini is detected via /opt/google/cros-containers/bin/garcon, which is present in every Crostini penguin container. ssh/tailssh extends the existing Debian default-PATH case to cover Crostini, since Crostini is Debian-based and benefits from the same SSH PATH defaults. RELNOTE: Crostini now defaults to userspace-networking. Fixes #19488 Updates #12090 Signed-off-by: ferrumclaudepilgrim --- cmd/tailscaled/tailscaled.go | 10 ++++++++++ ssh/tailssh/user.go | 2 +- version/distro/distro.go | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 42126f0c9..ef2b5ed1d 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -87,6 +87,16 @@ func defaultTunName() string { // See https://github.com/tailscale/tailscale-synology/issues/35 return "tailscale0,userspace-networking" } + if buildfeatures.HasNetstack && distro.Get() == distro.Crostini { + // cros-garcon NULL-derefs on cold-boot netlink interface + // enumeration when tailscale0 is present, preventing the + // Crostini container and ChromeOS Terminal from starting + // cleanly. Default to userspace-networking until the + // upstream ChromiumOS bug is fixed. + // See https://github.com/tailscale/tailscale/issues/12090 + // See https://issuetracker.google.com/issues/517069318 + return "userspace-networking" + } } return "tailscale0" } diff --git a/ssh/tailssh/user.go b/ssh/tailssh/user.go index 0d2bf31e7..8d7775cfb 100644 --- a/ssh/tailssh/user.go +++ b/ssh/tailssh/user.go @@ -93,7 +93,7 @@ func defaultPathForUser(u *user.User) string { } isRoot := u.Uid == "0" switch distro.Get() { - case distro.Debian: + case distro.Debian, distro.Crostini: hi := hostinfo.New() if hi.Distro == "ubuntu" { // distro.Get's Debian includes Ubuntu. But see if it's actually Ubuntu. diff --git a/version/distro/distro.go b/version/distro/distro.go index 03c02ccab..5407f8dae 100644 --- a/version/distro/distro.go +++ b/version/distro/distro.go @@ -19,6 +19,7 @@ const ( Debian = Distro("debian") + Crostini = Distro("crostini") // ChromeOS Crostini Linux container; Debian-based Arch = Distro("arch") Synology = Distro("synology") OpenWrt = Distro("openwrt") @@ -84,6 +85,11 @@ func linuxDistro() Distro { // Currently supported product families: // - UDM (UniFi Dream Machine, UDM-Pro) return UBNT + case have("/opt/google/cros-containers/bin/garcon"): + // ChromeOS Crostini ships /opt/google/cros-containers/bin/garcon + // in every penguin container. MUST be checked before Debian since + // Crostini is Debian-based. + return Crostini case have("/etc/debian_version"): return Debian case have("/etc/arch-release"):