165 lines
4.1 KiB
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")
|
|
}
|
|
}
|