Scert/backend/internal/localreset/transport_linux_test.go

165 lines
4.1 KiB
Go

//go:build linux
package localreset
import (
"context"
"errors"
"net"
"os"
"path/filepath"
"testing"
"time"
)
func TestUnixSocketRoundTrip(t *testing.T) {
path := filepath.Join(t.TempDir(), "reset.sock")
server, err := Listen(path)
if err != nil {
t.Fatal(err)
}
defer server.Close()
info, err := os.Lstat(path)
if err != nil || info.Mode().Perm() != 0600 {
t.Fatalf("unsafe socket permissions: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
done := make(chan error, 1)
session := &testSession{}
go func() {
done <- server.Serve(ctx, func(context.Context, Peer) (Session, error) { return session, nil })
}()
conn, err := Dial(ctx, path, uint32(os.Geteuid()))
if err != nil {
t.Fatal(err)
}
client, err := Begin(ctx, conn)
if err != nil {
t.Fatal(err)
}
defer client.Close()
if err := client.Select("recovery"); err != nil {
t.Fatal(err)
}
if err := client.Verify("test-code"); err != nil {
t.Fatal(err)
}
if err := client.Reset("Example123!"); err != nil {
t.Fatal(err)
}
cancel()
if err := <-done; err != nil {
t.Fatal(err)
}
if session.resets.Load() != 1 || session.closed.Load() != 1 {
t.Fatal("session lifecycle incomplete")
}
if _, err := os.Lstat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatal("owned socket not cleaned up")
}
}
func TestUnixRejectsUnsafeParent(t *testing.T) {
for _, mode := range []os.FileMode{0777, 0770} {
t.Run(mode.String(), func(t *testing.T) {
parent := t.TempDir()
if err := os.Chmod(parent, mode); err != nil {
t.Fatal(err)
}
if server, err := Listen(filepath.Join(parent, "reset.sock")); err == nil {
_ = server.Close()
t.Fatal("writable parent accepted")
}
})
}
parent := t.TempDir()
link := filepath.Join(parent, "link")
if err := os.Symlink(parent, link); err != nil {
t.Fatal(err)
}
if server, err := Listen(filepath.Join(link, "reset.sock")); err == nil {
_ = server.Close()
t.Fatal("symlink parent accepted")
}
}
func TestUnixRejectsUnsafePathsWithoutOverwriting(t *testing.T) {
parent := t.TempDir()
path := filepath.Join(parent, "reset.sock")
if err := os.WriteFile(path, []byte("preserve"), 0600); err != nil {
t.Fatal(err)
}
if server, err := Listen(path); err == nil {
_ = server.Close()
t.Fatal("regular file replaced")
}
data, err := os.ReadFile(path)
if err != nil || string(data) != "preserve" {
t.Fatal("existing file changed")
}
for _, path := range []string{"relative.sock", "tcp://127.0.0.1:80", parent + "/../reset.sock"} {
if server, err := Listen(path); err == nil {
_ = server.Close()
t.Fatalf("unsafe path accepted: %s", path)
}
}
}
func TestUnixActiveSocketCannotBeReplaced(t *testing.T) {
path := filepath.Join(t.TempDir(), "reset.sock")
first, err := Listen(path)
if err != nil {
t.Fatal(err)
}
defer first.Close()
if second, err := Listen(path); err == nil {
_ = second.Close()
t.Fatal("active socket replaced")
}
if _, err := Dial(context.Background(), path, uint32(os.Geteuid())+1); err == nil {
t.Fatal("wrong service identity accepted")
}
}
func TestUnixStaleOwnedSocketCanBeRecovered(t *testing.T) {
path := filepath.Join(t.TempDir(), "reset.sock")
old, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
t.Fatal(err)
}
old.SetUnlinkOnClose(false)
if err := os.Chmod(path, 0600); err != nil {
t.Fatal(err)
}
if err := old.Close(); err != nil {
t.Fatal(err)
}
server, err := Listen(path)
if err != nil {
t.Fatal(err)
}
defer server.Close()
}
func TestUnixClosePreservesReplacedSocketPath(t *testing.T) {
path := filepath.Join(t.TempDir(), "reset.sock")
server, err := Listen(path)
if err != nil {
t.Fatal(err)
}
if err := os.Rename(path, path+".old"); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte("replacement"), 0600); err != nil {
t.Fatal(err)
}
if err := server.Close(); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("replacement not detected: %v", err)
}
data, err := os.ReadFile(path)
if err != nil || string(data) != "replacement" {
t.Fatal("replacement removed")
}
}