Add Docker integration tests for TCP and Unix forwarding, pty size,
resize and modes, session env, envknobs, x11 rejection, session
teardown, SessionDuration, policy changes and RuleExpires on active
sessions, Shutdown, host keys and utmp, and run every integrationtest
test in the Dockerfile. Extend the vmtest SSH tests to use real
OpenSSH clients across the tailnet for pty, sftp/scp, agent and port
forwarding, AcceptEnv, ControlMaster, large transfers, banners, user
mapping, revocation, check mode (via a new testcontrol handler),
recording, and the tailscale ssh CLI. Fix make sshintegrationtest,
which only ever tested one image and always exited 0, and trigger the
workflow on go.mod changes and a schedule.
Updates #13038
Updates tailscale/corp#50380
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ie4fffad7e9298b6b77c3ec3a6ae81fd784d182ce
Let fetchSSHAction own the per-fetch deadline and pass the connection cancellation context directly from clientAuth. Remove the duplicate timeout and its deferred cancellation inside the action loop. Derive the longer transport watchdog from the fetch timeout, keeping both durations unchanged.
Use virtual time to test the fetch deadline across retries, earlier parent deadlines, and parent cancellation. Verify that a successful fetch releases its request context.
Updates tailscale/corp#50380
Change-Id: I185958ca19566f04cbd68f33e4b1dad3d18aebb7
Signed-off-by: James Tucker <james@tailscale.com>
Move lazy monitor startup, credentials, and cleanup into connmonitor.go and rename the connection state and accessors. The monitor owns session processes as well as forwarding children. Move the session exit registry alongside session monitor handling and keep the Plan 9 stub in a matching file.
Update callers, tests, and diagnostic prefixes without changing monitor lifecycle behavior.
Updates tailscale/corp#50380
Change-Id: I760fa009176491da426639615907d129b68026fc
Signed-off-by: James Tucker <james@tailscale.com>
Describe exit statuses, output ordering, and descriptor ownership directly. Keep the Darwin descriptor acknowledgment rationale in the protocol documentation instead of repeating it at each handoff.
Clarify that the exec spawn limit is checked after Start returns, not an interrupting deadline. Remove claims that session exit statuses are unique or that unreaped children must be uninterruptible.
Updates tailscale/corp#50380
Change-Id: I613b83ac2ae92d7bf8a582c4e4b326ebbe55852d
Signed-off-by: James Tucker <james@tailscale.com>
Check shutdown and register initialized connections under the same lock. Reject a connection if shutdown ran during its construction, stopping its login-grace timer and closing the transport. Also close the transport when connection construction fails.
Cover shutdown before and during connection construction without starting an SSH handshake.
Updates tailscale/corp#50380
Change-Id: I634036a7e073b24d7ff80d6a6d0ae991c40f3c69
Signed-off-by: James Tucker <james@tailscale.com>
Route direct-tcpip channels, used by TCP -L and SOCKS -D forwards, through the per-connection monitor after the existing policy checks. Resolve names and create/connect TCP sockets in a child running with the authenticated local user credentials instead of the root daemon.
Hand the connected TCP descriptor back through the existing acknowledged fd protocol. TCP socket ownership survives the handoff, so UID-based firewall policy sees the mapped user. TCP has no per-message SCM_CREDENTIALS and needs no persistent relay: the child exits after handing off the socket. Bound resolution/dialing by the monitor forwarding deadline.
Share the channel-close-aware forwarding copy loop with Unix forwards, retaining bidirectional half-closes and bounded draining on full close. Plan 9 retains its existing in-process forwarding path.
Add root-run IPv4/IPv6 socket-owner regression tests, hostname and half-close coverage, policy/disable gates, invalid/refused dial handling, and a pin that the child is reaped while its socket remains usable. Update the TCP policy revocation fixture for child spawning and synchronize its assertion on transport shutdown.
Updates tailscale/corp#50380
Change-Id: I89159fd31e75c22446096587471afcd2bb4e78d4
Signed-off-by: James Tucker <james@tailscale.com>
Drain any stale buffered size before sending the latest window update. The adapter is the only sender to a one-slot channel, so the final resize is retained even if the consumer races with the drain.
Cover late and slow readers over a real SSH session with deterministic synctest synchronization. Keep the final two resizes unread to exercise replacement of the buffered size.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I8c077114a6d4765c4208d42a22f384c82f73f75c
Keep the dialed service socket in the credentialed forwarding child and return a private socketpair endpoint instead. Passing the service socket to tailscaled preserves connect-time peer credentials but causes payload SCM_CREDENTIALS to identify the root daemon.
Keep dial children alive for bidirectional forwarding. Return a lifetime
pipe alongside the data socket so full closure or a discarded dial reply
ends the relay independently of data EOF. Preserve half-closes and the
monitor's descriptor acknowledgment/teardown ownership for both fds.
Listener children retain their existing descriptor handoff behavior.
Handle local Unix forwarding channel closure independently of the SSH
connection: the channel request stream closes on CHANNEL_CLOSE, not
CHANNEL_EOF. Stop waiting for service output while giving queued client
payload a bounded drain in each forwarding stage, so a silent or
non-reading service cannot retain the relay indefinitely.
Test relay exit/reaping with silent services, discarded replies, queued
payload and backpressure, including end-to-end SSH channel closure while
the transport stays connected. Fix a canary test's unsynchronized marker
assertion to wait for the reporting goroutine.
Add a root-run cross-UID SO_PASSCRED regression test and lifecycle coverage for both half-close directions and monitor shutdown with an idle relay.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I54b8ddf02b9d82434e474fea2ec283c0e39fab3e
The check for finding unambiguous disco pings was not comparing the
disco key and node keys correctly. Fix the error and add a test that
asserts the ping is not seen as unambiguous when the node key does not
match the disco key, and the disco key has multiple matches.
Fixestailscale/corp#50343
Thank you to Brody Franks for reporting this issue.
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Cancel fail-closed sessions before sending recording upload failure
notifications. Bound each notifyControl request to five seconds, including
recorder connection failures on the synchronous session startup path.
An unresponsive control request must not keep an unrecorded session running
or indefinitely delay rejection or fail-open startup.
Keep the timeout local to each notification, independent of the session
and upload lifetime, without adding another goroutine. Test blocked
startup notifications in both rejection and fail-open modes, and use
virtual time to cover earlier and simultaneous cancellation/deadlines.
Send SFTP recording rejection diagnostics to stderr, preserving the binary protocol on stdout and exit status 254. Add real SSH/recorder tests for fail-closed and fail-open behavior with blocked notifications, and an SFTP client regression test.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I270d624975a6a9115502a4915ee5ff28a67d714b
And merge the repeated don't fragment codepaths between Linux and Darwin.
Updates tailscale/corp#48168
Change-Id: I5aef408f037c8def99522b4d820c853a6a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Allocate the exclusion set before adding client-supplied usernames. A true Exclude entry otherwise panics when SSH is enabled.
Exercise the c2n handler with true, false, absent, and unknown exclusions, and cover the result cap and disabled-SSH behavior.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I1d9faf36e5809ccff888fc402329652e8b7d88c8
Re-evaluate authenticated connections on policy updates and at session
and forwarding gates. Close connections whose grants are revoked or
narrowed, including forwarding permissions, user identity, duration,
AcceptEnv, and effective recording requirements. Recheck rule-derived
AcceptEnv and recording configuration for delegated connections too.
Compare recorder lists with duplicate counts preserved so duplicate
entries cannot hide a change of recording destination.
Treat recording-failure changes in either direction as material,
including fail-open to fail-closed transitions. An inconclusive user
lookup retains the previous identity but does not skip other policy
checks, including for `"*": "="` mappings. An absent account or a changed
username, UID, or primary GID revokes the connection.
Preserve explicit getent failures when the os/user fallback cannot find
the account, instead of treating every lookup failure as account deletion.
Numeric UID mappings use LookupId in the fallback, not Lookup by name.
Known limitation: NSS can report a directory-only account as not found
(getent exit status 2) during an sssd/LDAP outage. If the fallback also
reports the account missing, this is indistinguishable from deletion and
still revokes the connection. This availability tradeoff remains accepted
for now; explicit command failures, timeouts, and malformed output do not
by themselves establish that an account was deleted.
Reject forwarding requests using the latched Allow bit before doing a
policy recheck. Forwarding gates still re-evaluate policy, but use the
latched account identity rather than spawning getent for every forwarded
connection (including each SSH -D request). Account identity is rechecked
at authentication completion, policy updates, rule expiry, and session
gates.
Re-arm rule-expiry timers under the policy recheck lock and prevent
re-arming after teardown. Add connection-level login grace, extending
it during delegation and stopping it only on SSH authentication
success, not on unsigned key queries or partial authentication.
Close the raw transport and cancel delegated authentication on teardown.
Add regression tests for these transitions, wait for the auth-completion
recheck before test policy flips, and release stalled recorder handlers
even on test failure.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Launch incubator sessions through the per-connection monitor, passing
stdio and the optional environment pipe via SCM_RIGHTS. tailscaled
retains the parent I/O descriptors. Keep direct launch when no
tailscaled binary is available, and on Plan 9.
Wait for dispatched exec replies even after session cancellation so
the session retains ownership of any child that starts. Publish exit
status through persistent completion state. Identify monitor children
by PID and exec request ID, and validate both when signaling. Remove
raw-PID signaling after monitor teardown.
Normalize incubator identity flags and reject alternate spellings
that could override them. The monitor RPC caller remains trusted to
supply the executable and other arguments; this is not a privilege
boundary against tailscaled.
Pass default TERM and locale values through the environment pipe,
preserving last-value-wins defaults and policy-forwarded precedence.
Use a separately owned close-on-exec PTY descriptor for resizing.
Add regression tests for launch cancellation, exit delivery, signal
identity, descriptor ownership, and environment handling.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Route tailssh's forwarding through the per-connection ssh-monitor, so
on unix forwarded sockets are dialed and bound by children with the
authenticated local user's credentials, never by root tailscaled.
- unix -L, direct-streamlocal: the lexical allowlist check stays in
tailscaled as coarse policy. The allowlist is home, /tmp, and
/run/user/<uid>. Relative and abstract paths are rejected. The dial
then runs as the user through the monitor. The EvalSymlinks
re-validation is dropped, and the kernel's DAC is the boundary. The
allowlist is capped. A home directory that cleans to "/" or is empty
would prefix-match every absolute path, and such an entry is dropped
instead. Paths at or beyond the sun_path capacity reject lexically
rather than dying at bind or dial with a generic EINVAL.
- unix -R, streamlocal-forward: the listener is created by a user-cred
child with umask-at-bind 0600 and unlink-if-owned. Closing it does
not unlink the socket, and the stale file on cancel is accepted.
- TCP -R, tcpip-forward: a tailssh-owned handler replaces gliderssh's
ForwardedTCPHandler. The bind goes through the monitor's listen op
with the local user's credentials, so privileged ports fail by
kernel construction for non-root-mapped users. Root-mapped users are
allowed, as in OpenSSH. Bind addresses are loopback-only unless
TS_SSH_FORWARD_GATEWAY_PORTS is set, the equivalent of sshd's
GatewayPorts.
- agent forwarding: the socket and its containing directory are
created by a user-cred child, a 0600 socket in a 0700 user-owned
directory, instead of a root-run MkdirTemp, Chmod, and Chown
sequence. Session-end cleanup is unchanged.
The monitor is spawned lazily on the first forwarding or agent need,
from the tailscaled binary, and closed when the SSH connection ends.
closeFwdMonitor latches a closed flag under the fwd lock, so a caller
racing the teardown is refused by fwdMonitor, or reads the current
state without spawning through fwdMonitorIfAny, instead of respawning
a monitor nobody ever closes.
plan9 keeps today's behavior through a build-tagged seam, fwdmonitor.go
and fwdplan9.go. There is no monitor on plan9. Unix forwarding is
rejected there, and reverse TCP forwarding keeps the gliderssh
handler with the mayReversePortForwardTo gate.
Tests cover conn-level callbacks against a real monitor and real
one-shot fwd children: allowlist rejections, user-owned 0600 sockets, a
foreign sticky-dir socket not unlinked, loopback, gateway-ports, and
privileged-port TCP binds, agent directory hygiene and cleanup, and
TS_SSH_DISABLE_FORWARDING coverage, plus refused respawn after
teardown. The unix-fwd root-dial regressions cover a static
in-allowlist symlink to a root-owned 0600 canary, which must fail
permission denied as the bound user, and a dir-swap race, which must
never serve the protected canary.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Add the one-shot "be-child ssh-fwd" children that perform forwarded
socket work, and the monitor and client plumbing for the socket ops.
The conn and session flows do not use the ops yet. The next commit
routes them.
- fwdchild.go: a child re-execed by the monitor, born unprivileged at
clone. The monitor applies the connection's bound identity as
SysProcAttr.Credential, so the kernel's DAC sees the connection
user's permissions. It dials a unix socket, or creates one with
umask-at-bind mode and optional unlink-if-owned, or binds a TCP
listener, or creates the agent-forwarding socket in a user-owned
directory. The result returns to the monitor over an inherited
socketpair as one status line with at most one descriptor attached.
- monitor.go: handleDial and handleListen run the ops on one-shot
children with a spawn-to-result deadline, monFwdOpDeadline. On
expiry the child's group is SIGKILLed. killChild reaps such a child
with a bounded non-blocking wait. A child left in uninterruptible
sleep is abandoned in its own group rather than blocking the loop
goroutine. The dial and listen placeholder replies are gone, and
their not-implemented test with them.
- monitor_client.go: Dial, Listen, and ListenAgent over the existing
request-ID plumbing.
- incubator.go, monitor_linux.go, monitor_other.go: registration and
per-platform spawn attributes for the fwd children.
Tests drive real children across uid boundaries. They cover dial and
bind round trips, unix and TCP listens, agent-listen directory
hygiene, unlink-if-owned, peer-credential assertions, and the
no-identity-keys-on-the-wire shape of every request.
Forwarding children spawn with an empty environment through
spawnRPCChild: they take everything from argv and the result socket,
and the daemon's environment must not reach a child running as the
connection's user.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
New self-contained code for the per-connection privileged ssh-monitor.
No existing behavior or wiring changes. Later commits wire the
forwarding and session paths onto it.
rpcFrame is the newline-delimited JSON wire struct with request IDs.
It covers dial, listen, exec, signal, and ping requests, ok and err
replies, and unsolicited exit notifications. rpcConn is the framing
codec over one socketpair end. Reads go only through ReadMsgUnix with
a CmsgSpace(3*sizeof(int)) OOB buffer. A plain Read or an undersized
buffer silently drops ancillary data, and the kernel closes the dropped
descriptors. A stream read never returns data beyond an
SCM_RIGHTS-carrying message, so a descriptor arrives with the first
chunk of a split line and survives reassembly. Writes attach
descriptors to the first byte of a line and never to an empty payload.
A short write resends the remainder as plain data, because resending
the ancillary data would duplicate the descriptors. Received
descriptors are marked close-on-exec at parse time, by the codec rather
than by the stdlib's platform-dependent handling. Herald attribution
validates a whole read's herald batch before queueing any set for a
frame, so a failed batch closes its sets exactly once and never
double-closes a set already queued for a frame. Lines are capped at
256 KiB, and NUL bytes and frames carrying more descriptors than their
op declares are rejected on receive. A read timeout returns
os.ErrDeadlineExceeded and preserves any partially received line, so
the monitor's poll loop can sweep between reads without losing the
frame. A read timeout is the only error that preserves state. Every
other error closes all held descriptors and clears the buffer.
sendFds, recvFds, and wrapUnixConn are shared by the monitor and client
sides. wrapUnixConn consumes its fd, because os.NewFile's finalizer
cannot be neutralized from outside.
Tests exercise ping and fd round-trips with liveness and FD_CLOEXEC
checks, multi-fd frames, kernel framing pins for first-chunk ancillary
delivery and undersized-OOB MSG_CTRUNC rejection, 64 KiB-plus
split-line reassembly under a shrunken SO_SNDBUF, oversized and NUL
rejection, the short-write resend path, the read-timeout
state-preservation contract, and 50-way multiplexed request/reply
traffic.
The monitor scrubs every descriptor from 3 up to close-on-exec at
entry and again before each exec. Go's exec closes nothing beyond the
descriptors it passes, so a single non-close-on-exec fd anywhere in the
spawner's table would otherwise reach every child the monitor execs.
close_range(CLOSE_RANGE_CLOEXEC) does the sweep in one syscall on
kernels 5.11+; older kernels scan the self-fd directory. A new test
injects a non-close-on-exec pipe at spawn and pins the clean-child
contract.
spawnRPCChild takes the child's environment explicitly: a nil env
gives the child an empty environment, never the spawner's. The monitor
child is spawned with the daemon's environment; user-identity children
are spawned with none, so the daemon's environment never reaches a
child running as the connection's user.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
ListenSSH now documents its session trust model and takes an
authorization callback. A session is accepted from any peer that the
tailnet ACL's TCP rules allow and that resolves with WhoIs. The server
performs no SSH client authentication, does not map the peer to a local
user, and does not record the session. The application is responsible
for what each session does. Set Server.SSHAuthCallback to reject
unwanted sessions before they reach Accept. A rejected session is
refused at open with exit status 1 and is never yielded to Accept. The
callback is plumbed through LocalBackend.ListenSSH and HookListenSSH.
With no callback, behavior is unchanged.
The listener's SSH server runs session channels only, with no port
forwarding. Closing the listener closes the accepted connections.
- DisablePTYEmulation runs on every session, so a PTY session passes
data through unchanged instead of translating "\n" to "\r\n". The
translation corrupted binary data read through the net.Conn
interface. Window changes are drained for every session, even when
the application never calls Pty, because gliderssh writes window
changes from the session request loop and an undrained channel
blocks the session's requests. Pty drops window changes when the
application is not reading them.
- The handshake is bounded by TS_SSH_LOGIN_GRACE_TIME, default 120
seconds, the same as sshd's LoginGraceTime.
- TS_SSH_IDLE_TIMEOUT bounds connections with no SSH traffic. It is 0
by default, and zero applies no bound, so quiet but live sessions
are not closed by default. Both timeout knobs accept a Go duration
such as "90s" or a bare number of seconds.
- Accepted sessions wait for Accept in a bounded queue. When the queue
is full, further sessions are refused, so an application that stops
calling Accept parks only a bounded number of sessions.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
sftpSubsystemSession waited for the stdout copier's done channel but not
the stderr copier's. The stderr copier carries the recording-policy
refusal notices. The deferred channel close could cut it off mid-drain.
The refusal text never reached the test's buffer. Both copiers are
now waited on before the channel closes.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
`serveWebHandler` wrote each mount's accepted app capabilities into the connection-scoped serveHTTPContext. Concurrent h2 streams of one TLS serve connection share that struct, so a request to a mount accepting no capabilities could be proxied carrying another mount's `Tailscale-App-Capabilities` header, letting backends that trust the header authorize the wrong mount.
This change fixes that issue by storing accepted capabilities per-request via a context value instead of a field on the shared struct.
Updates tailscale/corp#50380
Change-Id: I5d0a4a02266018f342ba75a1b3291176738ce05b
Signed-off-by: Mike Jensen <mikej@tailscale.com>
Remove the blanket sftp-subsystem exemption from session recording:
in a recording-mandatory tailnet, sftp sessions (and scp -s, which
rides the sftp subsystem) now go through recording setup with the same
OnRecordingFailure semantics — an unreachable recorder refuses the
session (exit 254) instead of letting file access proceed silently
unrecorded.
SFTP casts are metadata-only: the CastHeader identifies the peer, local
user, connection, and records Command = "sftp" (subsystem requests
carry no command string), but no I/O events are captured — the SFTP
wire protocol is binary, so pass-through capture would not be
human-meaningful and would balloon cast sizes. The upload/cleanup
lifecycle is unchanged. With no recorders configured, sftp is
unchanged.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
This change unifies all root checks in `ssh/tailssh` to use `isRootUser`. This ensures we consistently avoid the `== "0"` check and protects us in any future root check changes (previously missed in #21919).
Updates tailscale/corp#50380
Change-Id: I598e4394cf3a779e622fa208378b33afee8723e3
Signed-off-by: Mike Jensen <mikej@tailscale.com>
The add closure used len(res.Usernames) > max, letting one extra
username through the client's Max, and the client-supplied Max was used
unclamped. Honor Max exactly and clamp it to a 100-username ceiling;
the list is only a login hint.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
GLIBC_TUNABLES is parsed by glibc in root su/login environments with
AT_SECURE=0 (CVE-2023-4911 class); add it to the dangerous-env-var
denylist next to its GCONV_PATH/LOCPATH siblings.
The TERM/LANG/LC_* pairs accepted without policy (OpenSSH's default
AcceptEnv) had unvalidated values that reach root-exec'd login/su
environments, where glibc treats path-like locale names as locale-data
paths and leading dashes as its own options. Reject path- and
option-like values (and NUL, which would truncate on exec) instead of
trying to sanitize them.
Also cap accepted env var names at 256 bytes before any wildcard
matching, bounding the matcher input and the child's "su -w" allowlist;
real names are short (LOGIN_NAME_MAX).
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
When tailscaledPath is empty, newIncubatorCommand runs the session's
shell directly, with no be-child incubator to drop privileges. A root
tailscaled could therefore run a non-root user's session as root.
Refuse that case, mirroring canSwitchToLocalUser's semantics.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
The agent forwarding socket's temp directory was left root-owned and
chmod 0755, letting every local user reach other users' agent sockets.
Chown the directory to the session user and keep it 0700 (the socket is
already 0600 and chowned first), like sshd's agent forwarding, and
remove the directory when the session ends instead of only closing the
listener.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
A policy rejection names the requested sshUser: that name is the
client's own request and reflects only tailnet policy (the client's
own ACL view), never a local-host fact, so naming it leaks nothing.
What a client-visible rejection must never depend on is server-side
local-user information: local-user lookup failures send one fixed
message for every failure cause, with the underlying error logged
server-side only, so the client cannot tell "no such local user"
from any other lookup failure and no banner is a
username-existence oracle.
Pin both properties in TestSSHAuthFlow: the user-mismatch cases,
including a rule whose "=" mapping forces a local lookup of a user
that does not exist, all name the requested user identically, and an
accept rule whose mapped local user fails lookup gets the fixed
lookup-failure banner.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Private host keys are only ever read by tailscaled itself; writing them
0700 needlessly allows group/other to walk into the key directory.
Use 0600, and pin it in TestSSHKeyGen.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
An NSS uid like "00" passed the string != "0" check but parses to 0 in
the incubator, so a session could bypass non-root checks and run as
root. Compare the uid numerically instead; non-numeric uids keep the
previous behavior.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Before invoking 'id -Gz <user>', apply the same input validation as the
getent path (checkGetentInput), so a crafted username like '-u' can't be
interpreted as an option by the id command.
parseGetentUser now rejects multi-line (multiple or corrupted records)
output and validates the username and shell fields (no leading dashes,
control characters, or invalid UTF-8), the fields that are used to look
up and run a session. Also add the missing leading-dash regression test
for the 'id' call path.
The passwd format has no escaping — gecos sub-fields are comma-separated
— so an eighth colon-separated field means a stray colon in some field,
shifting every field after it: home parses as a gecos fragment and the
shell as the real home directory. Short records keep the existing
treatment (padded, with the default-shell fallback); over-long records
now fail closed like the other corrupt-output cases.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
GCONV_PATH, LOCPATH, NLSPATH, HOSTALIASES and RES_OPTIONS can make
privileged processes (root su/login environments built from forwarded
vars) load attacker-controlled files or redirect resolver lookups; add
them to the isDangerousEnvVar denylist, and apply the same check in the
incubator child when it re-filters the inherited forwarded-env payload
(old parents may not have filtered them).
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
matchAcceptEnvPattern recursively retried each '*' suffix, which is
exponential in the number of '*'s in an acceptEnv pattern (2.7s observed
on a 400-byte crafted variable name). Replace it with an iterative
matcher that only backtracks to the most recent '*', preserving the
exact OpenSSH-style glob semantics. Cross-check it against a naive
backtracking reference over random inputs, and pin the crafted
backtracking case as a timing regression test.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
The CastHeader recorded a shell-parsed reconstruction of the session
command, but the command is passed to the user's shell verbatim, and the
shell lexer silently drops unbalanced quotes. A spoofed command could
therefore make the recording differ from what the shell actually ran.
Record ss.RawCommand() instead.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
StatCache started each ttlcache with "go cache.Start()" and stopped
them in StatCache.stop. But ttlcache caches begin in the stopped
state, so if stop ran before the Start goroutine was scheduled,
Stop was a no-op and Start then ran forever, leaking a goroutine.
That made tests using tstest.ResourceCheck flaky:
goroutine github.com/jellydator/ttlcache/v3.(*Cache[...]).Start
Rather than synchronize with Start, drop the background goroutine
entirely. Cache.Get already ignores expired items, so the goroutine
only existed to free memory. Instead, call DeleteExpired on each
write, which is cheap: it pops expired entries from the front of the
expiration heap and stops at the first unexpired one. With no
goroutines, StatCache.stop is no longer needed, so remove it.
Fixes#21824
Updates tailscale/corp#49459
Change-Id: Ib438fcd45296be79528613c5f5687725c38a137d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Windows socket setup enumerates adapters and the routing table through
GetWindowsDefault for every netns socket. These calls can be expensive,
particularly on systems where security software adds syscall latency.
Cache nonzero default interface indices per monitor and address family,
and use them from netns controlC. Invalidate on native route, address,
and IP interface change notifications.
Coalesce concurrent lookups and retry results invalidated during lookup.
Bound lookup and wait attempts; once the budget is exhausted, fall back
to a single uncached lookup rather than failing socket setup, erring
toward a possibly stale interface index over no socket at all. Perform
fresh lookups for zero indices and errors, and retain the existing
unbound-socket fallback for a fresh zero result.
Use uncached lookups after monitor shutdown or when interface-change
notification registration fails.
Test cache reuse, independent address families, notification invalidation,
concurrent fills, invalidation during lookup, route restoration after a
zero result, churn and wait-budget fallbacks, and shutdown.
Fixes#21885
Signed-off-by: James Tucker <james@tailscale.com>
We already do this for `Set.OfSliceView`.
Updates #cleanup
Change-Id: I6ce3022ddabae2a0891e76ca45f3a7c601627a02
Signed-off-by: Alex Chan <alexc@tailscale.com>
This interprets HiddenNetworkDevices per watch session and sends GUI visibility metadata for peers (without filtering them). We refresh the visibility snapshot when policy or peer membership changes.
Updates tailscale/corp#44104
Signed-off-by: kari-ts <kari@tailscale.com>
In #20775 (and the follow-up in #21471), we started installing quad-100
as the OS's primary resolver whenever cfg has hosts not covered by a split
DNS route, so that sandboxed macOS clients could still answer them.
However, that check was added to the generic split DNS gate rather than
to the Apple-specific one, so it also applies on Windows, where dcfg.Hosts
always carries every node's records and MagicDNSHostsUnrouted is set
whenever MagicDNS is disabled. As a result, a Windows node with MagicDNS
off and at least one split DNS route (or with MagicDNS on and a peer shared
in from another tailnet) gets a catch-all NRPT rule pointing at quad-100,
and all DNS traffic on the device is proxied through it instead of only
the configured suffixes.
The check is redundant for Apple platforms: iOS never takes the scoped
path there, and sandboxed macOS only does so via scopeApple, which already
requires !cfg.requiresPrimaryResolver(). This removes it from the generic
gate, restoring the pre-1.104 behavior on Windows, and adds tests.
Updates tailscale/corp#45534Fixes#21863
Signed-off-by: Nick Khyl <nickk@tailscale.com>
Create a fresh directory for each Linux tarball download and remove it
after unpacking or a failed download. Continue to prefer the user cache
directory and use the temporary directory when the cache path is
unavailable.
Fixestailscale/corp#50119Fixestailscale/corp#48680
Change-Id: Iebe13d922f8fa691f26fe17d908b295b7153aadb
Credit: Winfunc
Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
Package unitconv implements string conversion functionality
for unit prefixes such as those from the SI and IEC standards.
It is catered towards printing of values for human consumption.
This exists to support open-sourcing JSON migration tooling.
Updates #20220
Updates tailscale/corp#791
Signed-off-by: Joe Tsai <joetsai@digital-static.net>