TS-2026-012
Vulnerability from tailscale - Published: Tue, 13 Oct 2026 00:00:00 GMT
Description: A bug in libtailscale's use of the SCM_RIGHTS file-descriptor passing API could attribute the wrong file descriptor to a connection, causing tailscale_getremoteaddr to return the wrong peer address.
What happened?
libtailscale embeds a full Tailscale node in applications written in other languages, exposing a C API that is wrapped by bindings such as TailscaleKit (Swift), the tailscale Ruby gem, and the tailscale Python package. Accepted connections are delivered to the application as file descriptors passed over a socketpair via the SCM_RIGHTS API, each accompanied by its remote address.
The listener's address table was keyed by the sender-side descriptor number, but lookups used the receiver-side descriptor number that recvmsg installs. The sender's number is closed immediately after the descriptor is sent and differs from the number the receiver holds. This worked by coincidence when descriptor allocation was predictable. However, under load, one connection's address could be attributed to another connection's descriptor, so tailscale_getremoteaddr returned the remote address of a different connection than the one asked about. In the connection copy path, the same confusion could act on a reused descriptor number, shutting down reads or writes on an unrelated descriptor in the embedding application.
libtailscale now sends each accepted connection's remote address as sendmsg data alongside the descriptor rights (one fixed-size record per connection, read exactly once per accept) and records it under the descriptor number the receiver actually holds. A descriptor number is never acted on after its file has been closed.
This vulnerability is fixed in libtailscale version 0.2.0 or newer.
What was the impact?
tailscale_getremoteaddr could return the remote address of a different connection than the one asked about. An unrelated file descriptor in the embedding application could be shut down for reads or writes, disrupting the application.
Who was affected?
Applications that embed Tailscale using libtailscale or its bindings (TailscaleKit, the tailscale Ruby gem, the tailscale Python package) and use the listener API or open connections through the library, built from libtailscale revisions prior to version 0.2.0. The standard Tailscale client and tailscaled-based deployments are not affected.
What do I need to do?
If you embed Tailscale via libtailscale, update to version 0.2.0 of the Ruby gem, Python package, or source distribution, or rebuild TailscaleKit from current libtailscale source. If your application made access or accounting decisions based on the address returned by tailscale_getremoteaddr, review those decisions and logs for the period it ran an affected build.
Credits
We would like to thank Barnabas Ratki of Douro Labs for reporting this issue.
Show details on source website{
"guidislink": false,
"id": "https://tailscale.com/security-bulletins/#ts-2026-012",
"link": "https://tailscale.com/security-bulletins/#ts-2026-012",
"links": [
{
"href": "https://tailscale.com/security-bulletins/#ts-2026-012",
"rel": "alternate",
"type": "text/html"
}
],
"published": "Tue, 13 Oct 2026 00:00:00 GMT",
"summary": "\u003cp\u003e\u003cstrong\u003e\u003cem\u003eDescription\u003c/em\u003e\u003c/strong\u003e: A bug in \u003ccode\u003elibtailscale\u003c/code\u003e\u0027s use of the \u003ccode\u003eSCM_RIGHTS\u003c/code\u003e file-descriptor passing API could attribute the wrong file descriptor to a connection, causing \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e to return the wrong peer address.\u003c/p\u003e\n\u003ch4\u003eWhat happened?\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tailscale/libtailscale\"\u003elibtailscale\u003c/a\u003e embeds a full Tailscale node in applications written in other languages, exposing a C API that is wrapped by bindings such as TailscaleKit (Swift), the \u003ccode\u003etailscale\u003c/code\u003e Ruby gem, and the \u003ccode\u003etailscale\u003c/code\u003e Python package. Accepted connections are delivered to the application as file descriptors passed over a socketpair via the \u003ca href=\"https://man7.org/linux/man-pages/man7/unix.7.html\"\u003eSCM_RIGHTS\u003c/a\u003e API, each accompanied by its remote address.\u003c/p\u003e\n\u003cp\u003eThe listener\u0027s address table was keyed by the sender-side descriptor number, but lookups used the receiver-side descriptor number that recvmsg installs. The sender\u0027s number is closed immediately after the descriptor is sent and differs from the number the receiver holds. This worked by coincidence when descriptor allocation was predictable. However, under load, one connection\u0027s address could be attributed to another connection\u0027s descriptor, so \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e returned the remote address of a different connection than the one asked about. In the connection copy path, the same confusion could act on a reused descriptor number, shutting down reads or writes on an unrelated descriptor in the embedding application.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003elibtailscale\u003c/code\u003e now sends each accepted connection\u0027s remote address as sendmsg data alongside the descriptor rights (one fixed-size record per connection, read exactly once per accept) and records it under the descriptor number the receiver actually holds. A descriptor number is never acted on after its file has been closed.\u003c/p\u003e\n\u003cp\u003eThis vulnerability is fixed in libtailscale version 0.2.0 or newer.\u003c/p\u003e\n\u003ch4\u003eWhat was the impact?\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e could return the remote address of a different connection than the one asked about. An unrelated file descriptor in the embedding application could be shut down for reads or writes, disrupting the application.\u003c/p\u003e\n\u003ch4\u003eWho was affected?\u003c/h4\u003e\n\u003cp\u003eApplications that embed Tailscale using \u003ccode\u003elibtailscale\u003c/code\u003e or its bindings (TailscaleKit, the \u003ccode\u003etailscale\u003c/code\u003e Ruby gem, the \u003ccode\u003etailscale\u003c/code\u003e Python package) and use the listener API or open connections through the library, built from libtailscale revisions prior to version 0.2.0. The standard Tailscale client and \u003ccode\u003etailscaled\u003c/code\u003e-based deployments are not affected.\u003c/p\u003e\n\u003ch4\u003eWhat do I need to do?\u003c/h4\u003e\n\u003cp\u003eIf you embed Tailscale via \u003ccode\u003elibtailscale\u003c/code\u003e, update to version 0.2.0 of the Ruby gem, Python package, or source distribution, or rebuild TailscaleKit from current \u003ccode\u003elibtailscale\u003c/code\u003e source. If your application made access or accounting decisions based on the address returned by \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e, review those decisions and logs for the period it ran an affected build.\u003c/p\u003e\n\u003ch4\u003eCredits\u003c/h4\u003e\n\u003cp\u003eWe would like to thank Barnabas Ratki of Douro Labs for reporting this issue.\u003c/p\u003e",
"summary_detail": {
"base": "https://tailscale.com/security-bulletins/index.xml",
"language": null,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e\u003cem\u003eDescription\u003c/em\u003e\u003c/strong\u003e: A bug in \u003ccode\u003elibtailscale\u003c/code\u003e\u0027s use of the \u003ccode\u003eSCM_RIGHTS\u003c/code\u003e file-descriptor passing API could attribute the wrong file descriptor to a connection, causing \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e to return the wrong peer address.\u003c/p\u003e\n\u003ch4\u003eWhat happened?\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tailscale/libtailscale\"\u003elibtailscale\u003c/a\u003e embeds a full Tailscale node in applications written in other languages, exposing a C API that is wrapped by bindings such as TailscaleKit (Swift), the \u003ccode\u003etailscale\u003c/code\u003e Ruby gem, and the \u003ccode\u003etailscale\u003c/code\u003e Python package. Accepted connections are delivered to the application as file descriptors passed over a socketpair via the \u003ca href=\"https://man7.org/linux/man-pages/man7/unix.7.html\"\u003eSCM_RIGHTS\u003c/a\u003e API, each accompanied by its remote address.\u003c/p\u003e\n\u003cp\u003eThe listener\u0027s address table was keyed by the sender-side descriptor number, but lookups used the receiver-side descriptor number that recvmsg installs. The sender\u0027s number is closed immediately after the descriptor is sent and differs from the number the receiver holds. This worked by coincidence when descriptor allocation was predictable. However, under load, one connection\u0027s address could be attributed to another connection\u0027s descriptor, so \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e returned the remote address of a different connection than the one asked about. In the connection copy path, the same confusion could act on a reused descriptor number, shutting down reads or writes on an unrelated descriptor in the embedding application.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003elibtailscale\u003c/code\u003e now sends each accepted connection\u0027s remote address as sendmsg data alongside the descriptor rights (one fixed-size record per connection, read exactly once per accept) and records it under the descriptor number the receiver actually holds. A descriptor number is never acted on after its file has been closed.\u003c/p\u003e\n\u003cp\u003eThis vulnerability is fixed in libtailscale version 0.2.0 or newer.\u003c/p\u003e\n\u003ch4\u003eWhat was the impact?\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e could return the remote address of a different connection than the one asked about. An unrelated file descriptor in the embedding application could be shut down for reads or writes, disrupting the application.\u003c/p\u003e\n\u003ch4\u003eWho was affected?\u003c/h4\u003e\n\u003cp\u003eApplications that embed Tailscale using \u003ccode\u003elibtailscale\u003c/code\u003e or its bindings (TailscaleKit, the \u003ccode\u003etailscale\u003c/code\u003e Ruby gem, the \u003ccode\u003etailscale\u003c/code\u003e Python package) and use the listener API or open connections through the library, built from libtailscale revisions prior to version 0.2.0. The standard Tailscale client and \u003ccode\u003etailscaled\u003c/code\u003e-based deployments are not affected.\u003c/p\u003e\n\u003ch4\u003eWhat do I need to do?\u003c/h4\u003e\n\u003cp\u003eIf you embed Tailscale via \u003ccode\u003elibtailscale\u003c/code\u003e, update to version 0.2.0 of the Ruby gem, Python package, or source distribution, or rebuild TailscaleKit from current \u003ccode\u003elibtailscale\u003c/code\u003e source. If your application made access or accounting decisions based on the address returned by \u003ccode\u003etailscale_getremoteaddr\u003c/code\u003e, review those decisions and logs for the period it ran an affected build.\u003c/p\u003e\n\u003ch4\u003eCredits\u003c/h4\u003e\n\u003cp\u003eWe would like to thank Barnabas Ratki of Douro Labs for reporting this issue.\u003c/p\u003e"
},
"title": "TS-2026-012",
"title_detail": {
"base": "https://tailscale.com/security-bulletins/index.xml",
"language": null,
"type": "text/plain",
"value": "TS-2026-012"
}
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.