mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-04 11:35:11 -04:00
79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
//go:build windows
|
|
|
|
package ipcauth
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// fileOwnerSID reads the owner SID of path the same way OpenOwnedFile does, so
|
|
// the test can construct an Identity that matches (or deliberately does not).
|
|
func fileOwnerSID(t *testing.T, path string) string {
|
|
t.Helper()
|
|
f, err := os.Open(path)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = f.Close() })
|
|
|
|
sd, err := windows.GetSecurityInfo(windows.Handle(f.Fd()), windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
|
|
require.NoError(t, err)
|
|
owner, _, err := sd.Owner()
|
|
require.NoError(t, err)
|
|
return owner.String()
|
|
}
|
|
|
|
// The allow branch of fileOwnedBy is the SID-equality path the legitimate GUI
|
|
// flow depends on. Running elevated, a created file is owned by
|
|
// BUILTIN\Administrators; an Identity carrying that SID with Elevated=false and
|
|
// no groups is unprivileged by IsPrivileged (which reads the token, not the
|
|
// SID's RID), so this exercises the real GetSecurityInfo equality rather than
|
|
// the privileged-caller shortcut.
|
|
func TestOpenOwnedFileWindowsOwnerMatchAllows(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "gui-client.log")
|
|
require.NoError(t, os.WriteFile(path, []byte("hello"), 0600))
|
|
|
|
ownerSID := fileOwnerSID(t, path)
|
|
id := Identity{SID: ownerSID}
|
|
require.False(t, id.IsPrivileged(), "identity built from the owner SID must be unprivileged for this to test the match path")
|
|
|
|
f, err := OpenOwnedFile(id, path)
|
|
require.NoError(t, err)
|
|
_ = f.Close()
|
|
}
|
|
|
|
func TestOpenOwnedFileWindowsOwnerMismatchRefuses(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "gui-client.log")
|
|
require.NoError(t, os.WriteFile(path, []byte("secret"), 0600))
|
|
|
|
other := Identity{SID: "S-1-5-21-9-9-9-9999"}
|
|
require.False(t, other.IsPrivileged())
|
|
|
|
_, err := OpenOwnedFile(other, path)
|
|
require.ErrorContains(t, err, "not owned by the caller")
|
|
}
|
|
|
|
// FILE_FLAG_OPEN_REPARSE_POINT must make OpenOwnedFile refuse a symlink the same
|
|
// way O_NOFOLLOW does on Unix, so a planted link can't redirect the read to
|
|
// another file. Creating a symlink needs a privilege the runner may lack, so the
|
|
// test skips rather than fails when it can't.
|
|
func TestOpenOwnedFileWindowsRefusesSymlink(t *testing.T) {
|
|
dir := t.TempDir()
|
|
target := filepath.Join(dir, "target.log")
|
|
require.NoError(t, os.WriteFile(target, []byte("secret"), 0600))
|
|
|
|
link := filepath.Join(dir, "gui-client.log")
|
|
if err := os.Symlink(target, link); err != nil {
|
|
t.Skipf("cannot create symlink (privilege not held?): %v", err)
|
|
}
|
|
|
|
id, err := CurrentProcessIdentity()
|
|
require.NoError(t, err)
|
|
|
|
_, err = OpenOwnedFile(id, link)
|
|
require.Error(t, err, "a symlink must be refused")
|
|
}
|