Skip to content
This repository was archived by the owner on Jul 13, 2025. It is now read-only.

Commit 9ea6126

Browse files
committed
Merge remote-tracking branch 'upstream/main'
# Conflicts: # .github/workflows/codeql-analysis.yml # .github/workflows/golangci-lint.yml # .github/workflows/test.yml # .github/workflows/update-flake.yml # .github/workflows/update-webclient-prebuilt.yml
2 parents 6e7642b + 51adaec commit 9ea6126

File tree

182 files changed

+7690
-2948
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+7690
-2948
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)
7272
`Signed-off-by` lines in commits.
7373

7474
See `git log` for our commit message style. It's basically the same as
75-
[Go's style](https://github.com/golang/go/wiki/CommitMessage).
75+
[Go's style](https://go.dev/wiki/CommitMessage).
7676

7777
## About Us
7878

appc/appconnector.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"sync"
1919
"time"
2020

21-
xmaps "golang.org/x/exp/maps"
2221
"golang.org/x/net/dns/dnsmessage"
2322
"tailscale.com/types/logger"
2423
"tailscale.com/types/views"
@@ -291,11 +290,11 @@ func (e *AppConnector) updateDomains(domains []string) {
291290
}
292291
}
293292
if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil {
294-
e.logf("failed to unadvertise routes on domain removal: %v: %v: %v", xmaps.Keys(oldDomains), toRemove, err)
293+
e.logf("failed to unadvertise routes on domain removal: %v: %v: %v", slicesx.MapKeys(oldDomains), toRemove, err)
295294
}
296295
}
297296

298-
e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.domains), e.wildcards)
297+
e.logf("handling domains: %v and wildcards: %v", slicesx.MapKeys(e.domains), e.wildcards)
299298
}
300299

301300
// updateRoutes merges the supplied routes into the currently configured routes. The routes supplied
@@ -354,7 +353,7 @@ func (e *AppConnector) Domains() views.Slice[string] {
354353
e.mu.Lock()
355354
defer e.mu.Unlock()
356355

357-
return views.SliceOf(xmaps.Keys(e.domains))
356+
return views.SliceOf(slicesx.MapKeys(e.domains))
358357
}
359358

360359
// DomainRoutes returns a map of domains to resolved IP

appc/appconnector_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
"testing"
1212
"time"
1313

14-
xmaps "golang.org/x/exp/maps"
1514
"golang.org/x/net/dns/dnsmessage"
1615
"tailscale.com/appc/appctest"
1716
"tailscale.com/tstest"
1817
"tailscale.com/util/clientmetric"
1918
"tailscale.com/util/mak"
2019
"tailscale.com/util/must"
20+
"tailscale.com/util/slicesx"
2121
)
2222

2323
func fakeStoreRoutes(*RouteInfo) error { return nil }
@@ -50,7 +50,7 @@ func TestUpdateDomains(t *testing.T) {
5050
// domains are explicitly downcased on set.
5151
a.UpdateDomains([]string{"UP.EXAMPLE.COM"})
5252
a.Wait(ctx)
53-
if got, want := xmaps.Keys(a.domains), []string{"up.example.com"}; !slices.Equal(got, want) {
53+
if got, want := slicesx.MapKeys(a.domains), []string{"up.example.com"}; !slices.Equal(got, want) {
5454
t.Errorf("got %v; want %v", got, want)
5555
}
5656
}

atomicfile/atomicfile.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import (
1515
)
1616

1717
// WriteFile writes data to filename+some suffix, then renames it into filename.
18-
// The perm argument is ignored on Windows. If the target filename already
19-
// exists but is not a regular file, WriteFile returns an error.
18+
// The perm argument is ignored on Windows, but if the target filename already
19+
// exists then the target file's attributes and ACLs are preserved. If the target
20+
// filename already exists but is not a regular file, WriteFile returns an error.
2021
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
2122
fi, err := os.Stat(filename)
2223
if err == nil && !fi.Mode().IsRegular() {
@@ -47,5 +48,5 @@ func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
4748
if err := f.Close(); err != nil {
4849
return err
4950
}
50-
return os.Rename(tmpName, filename)
51+
return rename(tmpName, filename)
5152
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
//go:build !windows
5+
6+
package atomicfile
7+
8+
import (
9+
"os"
10+
)
11+
12+
func rename(srcFile, destFile string) error {
13+
return os.Rename(srcFile, destFile)
14+
}

atomicfile/atomicfile_windows.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package atomicfile
5+
6+
import (
7+
"os"
8+
9+
"golang.org/x/sys/windows"
10+
)
11+
12+
func rename(srcFile, destFile string) error {
13+
// Use replaceFile when possible to preserve the original file's attributes and ACLs.
14+
if err := replaceFile(destFile, srcFile); err == nil || err != windows.ERROR_FILE_NOT_FOUND {
15+
return err
16+
}
17+
// destFile doesn't exist. Just do a normal rename.
18+
return os.Rename(srcFile, destFile)
19+
}
20+
21+
func replaceFile(destFile, srcFile string) error {
22+
destFile16, err := windows.UTF16PtrFromString(destFile)
23+
if err != nil {
24+
return err
25+
}
26+
27+
srcFile16, err := windows.UTF16PtrFromString(srcFile)
28+
if err != nil {
29+
return err
30+
}
31+
32+
return replaceFileW(destFile16, srcFile16, nil, 0, nil, nil)
33+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package atomicfile
5+
6+
import (
7+
"os"
8+
"testing"
9+
"unsafe"
10+
11+
"golang.org/x/sys/windows"
12+
)
13+
14+
var _SECURITY_RESOURCE_MANAGER_AUTHORITY = windows.SidIdentifierAuthority{[6]byte{0, 0, 0, 0, 0, 9}}
15+
16+
// makeRandomSID generates a SID derived from a v4 GUID.
17+
// This is basically the same algorithm used by browser sandboxes for generating
18+
// random SIDs.
19+
func makeRandomSID() (*windows.SID, error) {
20+
guid, err := windows.GenerateGUID()
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
rids := *((*[4]uint32)(unsafe.Pointer(&guid)))
26+
27+
var pSID *windows.SID
28+
if err := windows.AllocateAndInitializeSid(&_SECURITY_RESOURCE_MANAGER_AUTHORITY, 4, rids[0], rids[1], rids[2], rids[3], 0, 0, 0, 0, &pSID); err != nil {
29+
return nil, err
30+
}
31+
defer windows.FreeSid(pSID)
32+
33+
// Make a copy that lives on the Go heap
34+
return pSID.Copy()
35+
}
36+
37+
func getExistingFileSD(name string) (*windows.SECURITY_DESCRIPTOR, error) {
38+
const infoFlags = windows.DACL_SECURITY_INFORMATION
39+
return windows.GetNamedSecurityInfo(name, windows.SE_FILE_OBJECT, infoFlags)
40+
}
41+
42+
func getExistingFileDACL(name string) (*windows.ACL, error) {
43+
sd, err := getExistingFileSD(name)
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
dacl, _, err := sd.DACL()
49+
return dacl, err
50+
}
51+
52+
func addDenyACEForRandomSID(dacl *windows.ACL) (*windows.ACL, error) {
53+
randomSID, err := makeRandomSID()
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
randomSIDTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
59+
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_UNKNOWN,
60+
windows.TrusteeValueFromSID(randomSID)}
61+
62+
entries := []windows.EXPLICIT_ACCESS{
63+
{
64+
windows.GENERIC_ALL,
65+
windows.DENY_ACCESS,
66+
windows.NO_INHERITANCE,
67+
randomSIDTrustee,
68+
},
69+
}
70+
71+
return windows.ACLFromEntries(entries, dacl)
72+
}
73+
74+
func setExistingFileDACL(name string, dacl *windows.ACL) error {
75+
return windows.SetNamedSecurityInfo(name, windows.SE_FILE_OBJECT,
76+
windows.DACL_SECURITY_INFORMATION, nil, nil, dacl, nil)
77+
}
78+
79+
// makeOrigFileWithCustomDACL creates a new, temporary file with a custom
80+
// DACL that we can check for later. It returns the name of the temporary
81+
// file and the security descriptor for the file in SDDL format.
82+
func makeOrigFileWithCustomDACL() (name, sddl string, err error) {
83+
f, err := os.CreateTemp("", "foo*.tmp")
84+
if err != nil {
85+
return "", "", err
86+
}
87+
name = f.Name()
88+
if err := f.Close(); err != nil {
89+
return "", "", err
90+
}
91+
f = nil
92+
defer func() {
93+
if err != nil {
94+
os.Remove(name)
95+
}
96+
}()
97+
98+
dacl, err := getExistingFileDACL(name)
99+
if err != nil {
100+
return "", "", err
101+
}
102+
103+
// Add a harmless, deny-only ACE for a random SID that isn't used for anything
104+
// (but that we can check for later).
105+
dacl, err = addDenyACEForRandomSID(dacl)
106+
if err != nil {
107+
return "", "", err
108+
}
109+
110+
if err := setExistingFileDACL(name, dacl); err != nil {
111+
return "", "", err
112+
}
113+
114+
sd, err := getExistingFileSD(name)
115+
if err != nil {
116+
return "", "", err
117+
}
118+
119+
return name, sd.String(), nil
120+
}
121+
122+
func TestPreserveSecurityInfo(t *testing.T) {
123+
// Make a test file with a custom ACL.
124+
origFileName, want, err := makeOrigFileWithCustomDACL()
125+
if err != nil {
126+
t.Fatalf("makeOrigFileWithCustomDACL returned %v", err)
127+
}
128+
t.Cleanup(func() {
129+
os.Remove(origFileName)
130+
})
131+
132+
if err := WriteFile(origFileName, []byte{}, 0); err != nil {
133+
t.Fatalf("WriteFile returned %v", err)
134+
}
135+
136+
// We expect origFileName's security descriptor to be unchanged despite
137+
// the WriteFile call.
138+
sd, err := getExistingFileSD(origFileName)
139+
if err != nil {
140+
t.Fatalf("getExistingFileSD(%q) returned %v", origFileName, err)
141+
}
142+
143+
if got := sd.String(); got != want {
144+
t.Errorf("security descriptor comparison failed: got %q, want %q", got, want)
145+
}
146+
}

atomicfile/mksyscall.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package atomicfile
5+
6+
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
7+
8+
//sys replaceFileW(replaced *uint16, replacement *uint16, backup *uint16, flags uint32, exclude unsafe.Pointer, reserved unsafe.Pointer) (err error) [int32(failretval)==0] = kernel32.ReplaceFileW

atomicfile/zsyscall_windows.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
//go:build cgo || !darwin
55

6-
package main
6+
package systray
77

88
import (
99
"bytes"

0 commit comments

Comments
 (0)