diff --git a/VERSION.txt b/VERSION.txt index a63cb35e6f0b4..154cb93b2cc63 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.52.0 +1.52.1 diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 2c49d9350a8c1..1ec3578acae39 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -279,6 +279,7 @@ func (up *Updater) updateSynology() error { return nil } + up.cleanupOldDownloads(filepath.Join(os.TempDir(), "tailscale-update*", "*.spk")) // Download the SPK into a temporary directory. spkDir, err := os.MkdirTemp("", "tailscale-update") if err != nil { @@ -733,6 +734,7 @@ func (up *Updater) updateWindows() error { if err := os.MkdirAll(msiDir, 0700); err != nil { return err } + up.cleanupOldDownloads(filepath.Join(msiDir, "*.msi")) pkgsPath := fmt.Sprintf("%s/tailscale-setup-%s-%s.msi", up.track, ver, arch) msiTarget := filepath.Join(msiDir, path.Base(pkgsPath)) if err := up.downloadURLToFile(pkgsPath, msiTarget); err != nil { @@ -821,6 +823,30 @@ func (up *Updater) installMSI(msi string) error { return err } +// cleanupOldDownloads removes all files matching glob (see filepath.Glob). +// Only regular files are removed, so the glob must match specific files and +// not directories. +func (up *Updater) cleanupOldDownloads(glob string) { + matches, err := filepath.Glob(glob) + if err != nil { + up.Logf("cleaning up old downloads: %v", err) + return + } + for _, m := range matches { + s, err := os.Lstat(m) + if err != nil { + up.Logf("cleaning up old downloads: %v", err) + continue + } + if !s.Mode().IsRegular() { + continue + } + if err := os.Remove(m); err != nil { + up.Logf("cleaning up old downloads: %v", err) + } + } +} + func msiUUIDForVersion(ver string) string { arch := runtime.GOARCH if arch == "386" { diff --git a/clientupdate/clientupdate_test.go b/clientupdate/clientupdate_test.go index dc300341fc260..36e4e18cc5442 100644 --- a/clientupdate/clientupdate_test.go +++ b/clientupdate/clientupdate_test.go @@ -11,6 +11,8 @@ import ( "maps" "os" "path/filepath" + "slices" + "sort" "strings" "testing" ) @@ -683,3 +685,113 @@ func TestWriteFileSymlink(t *testing.T) { } } } + +func TestCleanupOldDownloads(t *testing.T) { + tests := []struct { + desc string + before []string + symlinks map[string]string + glob string + after []string + }{ + { + desc: "MSIs", + before: []string{ + "MSICache/tailscale-1.0.0.msi", + "MSICache/tailscale-1.1.0.msi", + "MSICache/readme.txt", + }, + glob: "MSICache/*.msi", + after: []string{ + "MSICache/readme.txt", + }, + }, + { + desc: "SPKs", + before: []string{ + "tmp/tailscale-update-1/tailscale-1.0.0.spk", + "tmp/tailscale-update-2/tailscale-1.1.0.spk", + "tmp/readme.txt", + "tmp/tailscale-update-3", + "tmp/tailscale-update-4/tailscale-1.3.0", + }, + glob: "tmp/tailscale-update*/*.spk", + after: []string{ + "tmp/readme.txt", + "tmp/tailscale-update-3", + "tmp/tailscale-update-4/tailscale-1.3.0", + }, + }, + { + desc: "empty-target", + before: []string{}, + glob: "tmp/tailscale-update*/*.spk", + after: []string{}, + }, + { + desc: "keep-dirs", + before: []string{ + "tmp/tailscale-update-1/tailscale-1.0.0.spk", + }, + glob: "tmp/tailscale-update*", + after: []string{ + "tmp/tailscale-update-1/tailscale-1.0.0.spk", + }, + }, + { + desc: "no-follow-symlinks", + before: []string{ + "MSICache/tailscale-1.0.0.msi", + "MSICache/tailscale-1.1.0.msi", + "MSICache/readme.txt", + }, + symlinks: map[string]string{ + "MSICache/tailscale-1.3.0.msi": "MSICache/tailscale-1.0.0.msi", + "MSICache/tailscale-1.4.0.msi": "MSICache/readme.txt", + }, + glob: "MSICache/*.msi", + after: []string{ + "MSICache/tailscale-1.3.0.msi", + "MSICache/tailscale-1.4.0.msi", + "MSICache/readme.txt", + }, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + dir := t.TempDir() + for _, p := range tt.before { + if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(p)), 0700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, p), []byte(tt.desc), 0600); err != nil { + t.Fatal(err) + } + } + for from, to := range tt.symlinks { + if err := os.Symlink(filepath.Join(dir, to), filepath.Join(dir, from)); err != nil { + t.Fatal(err) + } + } + + up := &Updater{Arguments: Arguments{Logf: t.Logf}} + up.cleanupOldDownloads(filepath.Join(dir, tt.glob)) + + var after []string + if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() { + after = append(after, strings.TrimPrefix(filepath.ToSlash(path), filepath.ToSlash(dir)+"/")) + } + return nil + }); err != nil { + t.Fatal(err) + } + + sort.Strings(after) + sort.Strings(tt.after) + if !slices.Equal(after, tt.after) { + t.Errorf("got files after cleanup: %q, want: %q", after, tt.after) + } + }) + } +} diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index e3e08f28a5e2b..e8f901a66db7d 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -30,6 +30,7 @@ import ( "os" "os/exec" "os/signal" + "path/filepath" "sync" "syscall" "time" @@ -299,6 +300,14 @@ func beWindowsSubprocess() bool { } }() + // Pre-load wintun.dll using a fully-qualified path so that wintun-go + // loads our copy and not some (possibly outdated) copy dropped in system32. + // (OSS Issue #10023) + fqWintunPath := fullyQualifiedWintunPath(log.Printf) + if _, err := windows.LoadDLL(fqWintunPath); err != nil { + log.Printf("Error pre-loading \"%s\": %v", fqWintunPath, err) + } + sys := new(tsd.System) netMon, err := netmon.New(log.Printf) if err != nil { @@ -507,7 +516,7 @@ func babysitProc(ctx context.Context, args []string, logf logger.Logf) { } func uninstallWinTun(logf logger.Logf) { - dll := windows.NewLazyDLL("wintun.dll") + dll := windows.NewLazyDLL(fullyQualifiedWintunPath(logf)) if err := dll.Load(); err != nil { logf("Cannot load wintun.dll for uninstall: %v", err) return @@ -517,3 +526,16 @@ func uninstallWinTun(logf logger.Logf) { err := wintun.Uninstall() logf("Uninstall: %v", err) } + +func fullyQualifiedWintunPath(logf logger.Logf) string { + var dir string + var buf [windows.MAX_PATH]uint16 + length := uint32(len(buf)) + if err := windows.QueryFullProcessImageName(windows.CurrentProcess(), 0, &buf[0], &length); err != nil { + logf("QueryFullProcessImageName failed: %v", err) + } else { + dir = filepath.Dir(windows.UTF16ToString(buf[:length])) + } + + return filepath.Join(dir, "wintun.dll") +}