internal/sys: separate hsu uid cache
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 4m34s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Sandbox (push) Successful in 1m23s
Test / Flake checks (push) Successful in 1m22s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 4m34s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Sandbox (push) Successful in 1m23s
Test / Flake checks (push) Successful in 1m22s
This begins the effort of the removal of the sys package. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
8690419c2d
commit
a2a291791c
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"hakurei.app/internal/app"
|
"hakurei.app/internal/app"
|
||||||
"hakurei.app/internal/app/state"
|
"hakurei.app/internal/app/state"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
"hakurei.app/system"
|
"hakurei.app/system"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
@ -272,20 +274,19 @@ func runApp(config *hst.Config) {
|
|||||||
// fatal prints the error message according to [container.GetErrorMessage], or fallback
|
// fatal prints the error message according to [container.GetErrorMessage], or fallback
|
||||||
// prepended to err if an error message is not available, followed by a call to [os.Exit](1).
|
// prepended to err if an error message is not available, followed by a call to [os.Exit](1).
|
||||||
func fatal(fallback string, err error) {
|
func fatal(fallback string, err error) {
|
||||||
m, ok := container.GetErrorMessage(err)
|
|
||||||
if !ok {
|
|
||||||
log.Fatal(fallback, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// this indicates the error message has already reached stderr, outside the current process's control;
|
// this indicates the error message has already reached stderr, outside the current process's control;
|
||||||
// this is only reached when hsu fails for any reason, as we do not want a second error message following hsu
|
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
||||||
// TODO(ophestra): handle the hsu error here instead of relying on a magic string
|
if errors.Is(err, sys.ErrHsuAccess) {
|
||||||
if m == "\x00" {
|
|
||||||
hlog.Verbose("*"+fallback, err)
|
hlog.Verbose("*"+fallback, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m, ok := container.GetErrorMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Fatalln(fallback, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Fatal(m)
|
log.Fatal(m)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/hlog"
|
"hakurei.app/internal/hlog"
|
||||||
|
"hakurei.app/internal/sys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrintRunStateErr prints an error message via [log] if runErr is not nil, and returns an appropriate exit code.
|
// PrintRunStateErr prints an error message via [log] if runErr is not nil, and returns an appropriate exit code.
|
||||||
@ -98,13 +99,20 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
|||||||
|
|
||||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
||||||
func printMessageError(fallback string, err error) {
|
func printMessageError(fallback string, err error) {
|
||||||
if m, ok := container.GetErrorMessage(err); ok {
|
// this indicates the error message has already reached stderr, outside the current process's control;
|
||||||
if m != "\x00" {
|
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
||||||
log.Print(m)
|
if errors.Is(err, sys.ErrHsuAccess) {
|
||||||
}
|
hlog.Verbose("*"+fallback, err)
|
||||||
} else {
|
return
|
||||||
log.Println(fallback, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m, ok := container.GetErrorMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Println(fallback, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save.
|
// StateStoreError is returned for a failed state save.
|
||||||
|
81
internal/sys/hsu.go
Normal file
81
internal/sys/hsu.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hsu caches responses from cmd/hsu.
|
||||||
|
type Hsu struct {
|
||||||
|
uidOnce sync.Once
|
||||||
|
uidCopy map[int]struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
uidMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrHsuAccess = errors.New("current user is not in the hsurc file")
|
||||||
|
|
||||||
|
func (h *Hsu) Uid(identity int) (int, error) {
|
||||||
|
h.uidOnce.Do(func() {
|
||||||
|
h.uidCopy = make(map[int]struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
h.uidMu.RLock()
|
||||||
|
u, ok := h.uidCopy[identity]
|
||||||
|
h.uidMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return u.uid, u.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.uidMu.Lock()
|
||||||
|
defer h.uidMu.Unlock()
|
||||||
|
|
||||||
|
u := struct {
|
||||||
|
uid int
|
||||||
|
err error
|
||||||
|
}{}
|
||||||
|
defer func() { h.uidCopy[identity] = u }()
|
||||||
|
|
||||||
|
u.uid = -1
|
||||||
|
hsuPath := internal.MustHsuPath()
|
||||||
|
|
||||||
|
cmd := exec.Command(hsuPath)
|
||||||
|
cmd.Path = hsuPath
|
||||||
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
|
cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
|
||||||
|
cmd.Dir = container.FHSRoot
|
||||||
|
var (
|
||||||
|
p []byte
|
||||||
|
exitError *exec.ExitError
|
||||||
|
)
|
||||||
|
|
||||||
|
const step = "obtain uid from hsu"
|
||||||
|
if p, u.err = cmd.Output(); u.err == nil {
|
||||||
|
u.uid, u.err = strconv.Atoi(string(p))
|
||||||
|
if u.err != nil {
|
||||||
|
u.err = &hst.AppError{Step: step, Err: u.err, Msg: "invalid uid string from hsu"}
|
||||||
|
}
|
||||||
|
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||||
|
// hsu prints an error message in this case
|
||||||
|
u.err = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||||
|
} else if os.IsNotExist(u.err) {
|
||||||
|
u.err = &hst.AppError{Step: step, Err: os.ErrNotExist,
|
||||||
|
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
||||||
|
}
|
||||||
|
return u.uid, u.err
|
||||||
|
}
|
@ -2,16 +2,13 @@ package sys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"hakurei.app/container"
|
"hakurei.app/container"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
@ -23,13 +20,7 @@ import (
|
|||||||
type Std struct {
|
type Std struct {
|
||||||
paths hst.Paths
|
paths hst.Paths
|
||||||
pathsOnce sync.Once
|
pathsOnce sync.Once
|
||||||
|
Hsu
|
||||||
uidOnce sync.Once
|
|
||||||
uidCopy map[int]struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
uidMu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Std) Getuid() int { return os.Getuid() }
|
func (s *Std) Getuid() int { return os.Getuid() }
|
||||||
@ -53,81 +44,27 @@ func (s *Std) Paths() hst.Paths {
|
|||||||
s.pathsOnce.Do(func() {
|
s.pathsOnce.Do(func() {
|
||||||
if userid, err := GetUserID(s); err != nil {
|
if userid, err := GetUserID(s); err != nil {
|
||||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
||||||
if m, ok := container.GetErrorMessage(err); ok {
|
|
||||||
if m != "\x00" {
|
|
||||||
log.Print(m)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Println("cannot obtain user id from hsu:", err)
|
|
||||||
}
|
|
||||||
hlog.BeforeExit()
|
hlog.BeforeExit()
|
||||||
s.Exit(1)
|
const fallback = "cannot obtain user id from hsu:"
|
||||||
|
|
||||||
|
// this indicates the error message has already reached stderr, outside the current process's control;
|
||||||
|
// this is only reached when hsu fails for any reason, as a second error message following hsu is confusing
|
||||||
|
if errors.Is(err, ErrHsuAccess) {
|
||||||
|
hlog.Verbose("*"+fallback, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m, ok := container.GetErrorMessage(err)
|
||||||
|
if !ok {
|
||||||
|
log.Fatalln(fallback, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(m)
|
||||||
} else {
|
} else {
|
||||||
CopyPaths(s, &s.paths, userid)
|
CopyPaths(s, &s.paths, userid)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return s.paths
|
return s.paths
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a temporary placeholder until this package is removed
|
|
||||||
type wrappedError struct {
|
|
||||||
Err error
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *wrappedError) Error() string { return e.Err.Error() }
|
|
||||||
func (e *wrappedError) Unwrap() error { return e.Err }
|
|
||||||
func (e *wrappedError) Message() string { return e.Msg }
|
|
||||||
|
|
||||||
func (s *Std) Uid(identity int) (int, error) {
|
|
||||||
s.uidOnce.Do(func() {
|
|
||||||
s.uidCopy = make(map[int]struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
{
|
|
||||||
s.uidMu.RLock()
|
|
||||||
u, ok := s.uidCopy[identity]
|
|
||||||
s.uidMu.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return u.uid, u.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.uidMu.Lock()
|
|
||||||
defer s.uidMu.Unlock()
|
|
||||||
|
|
||||||
u := struct {
|
|
||||||
uid int
|
|
||||||
err error
|
|
||||||
}{}
|
|
||||||
defer func() { s.uidCopy[identity] = u }()
|
|
||||||
|
|
||||||
u.uid = -1
|
|
||||||
hsuPath := internal.MustHsuPath()
|
|
||||||
|
|
||||||
cmd := exec.Command(hsuPath)
|
|
||||||
cmd.Path = hsuPath
|
|
||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
|
||||||
cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
|
|
||||||
cmd.Dir = container.FHSRoot
|
|
||||||
var (
|
|
||||||
p []byte
|
|
||||||
exitError *exec.ExitError
|
|
||||||
)
|
|
||||||
|
|
||||||
if p, u.err = cmd.Output(); u.err == nil {
|
|
||||||
u.uid, u.err = strconv.Atoi(string(p))
|
|
||||||
if u.err != nil {
|
|
||||||
u.err = &wrappedError{u.err, "invalid uid string from hsu"}
|
|
||||||
}
|
|
||||||
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
|
||||||
// hsu prints an error message in this case
|
|
||||||
u.err = &wrappedError{syscall.EACCES, "\x00"} // this drops the message, handled in cmd/hakurei/command.go
|
|
||||||
} else if os.IsNotExist(u.err) {
|
|
||||||
u.err = &wrappedError{os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
|
||||||
}
|
|
||||||
return u.uid, u.err
|
|
||||||
}
|
|
||||||
|
@ -8,9 +8,7 @@ NODE_GROUPS = ["nodes", "floating_nodes"]
|
|||||||
def swaymsg(command: str = "", succeed=True, type="command"):
|
def swaymsg(command: str = "", succeed=True, type="command"):
|
||||||
assert command != "" or type != "command", "Must specify command or type"
|
assert command != "" or type != "command", "Must specify command or type"
|
||||||
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
||||||
with machine.nested(
|
with machine.nested(f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)):
|
||||||
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
|
|
||||||
):
|
|
||||||
ret = (machine.succeed if succeed else machine.execute)(
|
ret = (machine.succeed if succeed else machine.execute)(
|
||||||
f"su - alice -c {shell}"
|
f"su - alice -c {shell}"
|
||||||
)
|
)
|
||||||
@ -102,7 +100,7 @@ print(machine.fail("sudo -u alice -i hsu"))
|
|||||||
# Verify hsu fault behaviour:
|
# Verify hsu fault behaviour:
|
||||||
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
|
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
|
||||||
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
||||||
if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot obtain uid from setuid wrapper: permission denied\n":
|
if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot obtain uid from setuid wrapper: current user is not in the hsurc file\n":
|
||||||
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
||||||
|
|
||||||
check_offset = 0
|
check_offset = 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user