sandbox: wrap fmsg interface
Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
ee10860357
commit
9a1f8e129f
@ -38,10 +38,10 @@ func init() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||||
sandbox.TryArgv0()
|
sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg)
|
||||||
init0.TryArgv0()
|
init0.TryArgv0()
|
||||||
|
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
|
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
|
||||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
// not fatal: this program runs as the privileged user
|
// not fatal: this program runs as the privileged user
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/helper"
|
"git.gensokyo.uk/security/fortify/helper"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -244,5 +245,6 @@ func TestHelperInit(t *testing.T) {
|
|||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
if len(os.Args) != 5 || os.Args[4] != "init" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sandbox.Init(internal.Exit)
|
sandbox.SetOutput(fmsg.Output{})
|
||||||
|
sandbox.Init(fmsg.Prepare, internal.InstallFmsg)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper"
|
"git.gensokyo.uk/security/fortify/helper"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,5 +52,6 @@ func TestHelperInit(t *testing.T) {
|
|||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
if len(os.Args) != 5 || os.Args[4] != "init" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sandbox.Init(internal.Exit)
|
sandbox.SetOutput(fmsg.Output{})
|
||||||
|
sandbox.Init(fmsg.Prepare, func(bool) { internal.InstallFmsg(false) })
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InternalHelperStub is an internal function but exported because it is cross-package;
|
// InternalHelperStub is an internal function but exported because it is cross-package;
|
||||||
@ -36,7 +35,7 @@ func InternalHelperStub() {
|
|||||||
genericStub(flagRestoreFiles(3, ap, sp))
|
genericStub(flagRestoreFiles(3, ap, sp))
|
||||||
}
|
}
|
||||||
|
|
||||||
internal.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(fd int, name, p string) *os.File {
|
func newFile(fd int, name, p string) *os.File {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -28,7 +29,7 @@ func Main() {
|
|||||||
fmsg.Prepare("init0")
|
fmsg.Prepare("init0")
|
||||||
|
|
||||||
// setting this prevents ptrace
|
// setting this prevents ptrace
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
|
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
|
||||||
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ func Main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// die with parent
|
// die with parent
|
||||||
if err := internal.SetPdeathsig(syscall.SIGKILL); err != nil {
|
if err := sandbox.SetPdeathsig(syscall.SIGKILL); err != nil {
|
||||||
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
log.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,10 +231,6 @@ func (seal *outcome) finalise(sys sys.State, config *fst.Config) error {
|
|||||||
sc := sys.Paths()
|
sc := sys.Paths()
|
||||||
seal.runDirPath = sc.RunDirPath
|
seal.runDirPath = sc.RunDirPath
|
||||||
seal.sys = system.New(seal.user.uid.unwrap())
|
seal.sys = system.New(seal.user.uid.unwrap())
|
||||||
seal.sys.IsVerbose = fmsg.Load
|
|
||||||
seal.sys.Verbose = fmsg.Verbose
|
|
||||||
seal.sys.Verbosef = fmsg.Verbosef
|
|
||||||
seal.sys.WrapErr = fmsg.WrapError
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Work directories
|
Work directories
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app/init0"
|
"git.gensokyo.uk/security/fortify/internal/app/init0"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// everything beyond this point runs as unconstrained target user
|
// everything beyond this point runs as unconstrained target user
|
||||||
@ -28,7 +29,7 @@ func Main() {
|
|||||||
fmsg.Prepare("shim")
|
fmsg.Prepare("shim")
|
||||||
|
|
||||||
// setting this prevents ptrace
|
// setting this prevents ptrace
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
|
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
|
||||||
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
internal/fmsg/msg.go
Normal file
12
internal/fmsg/msg.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package fmsg
|
||||||
|
|
||||||
|
type Output struct{}
|
||||||
|
|
||||||
|
func (Output) IsVerbose() bool { return Load() }
|
||||||
|
func (Output) Verbose(v ...any) { Verbose(v...) }
|
||||||
|
func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
|
||||||
|
func (Output) WrapErr(err error, a ...any) error { return WrapError(err, a...) }
|
||||||
|
func (Output) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) }
|
||||||
|
func (Output) Suspend() { Suspend() }
|
||||||
|
func (Output) Resume() bool { return Resume() }
|
||||||
|
func (Output) BeforeExit() { BeforeExit() }
|
@ -2,11 +2,15 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
"git.gensokyo.uk/security/fortify/seccomp"
|
"git.gensokyo.uk/security/fortify/seccomp"
|
||||||
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InstallFmsg(verbose bool) {
|
func InstallFmsg(verbose bool) {
|
||||||
fmsg.Store(verbose)
|
fmsg.Store(verbose)
|
||||||
|
sandbox.SetOutput(fmsg.Output{})
|
||||||
|
system.SetOutput(fmsg.Output{})
|
||||||
if verbose {
|
if verbose {
|
||||||
seccomp.SetOutput(fmsg.Verbose)
|
seccomp.SetOutput(fmsg.Verbose)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const (
|
|
||||||
SUID_DUMP_DISABLE = iota
|
|
||||||
SUID_DUMP_USER
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetDumpable(dumpable uintptr) error {
|
|
||||||
// linux/sched/coredump.h
|
|
||||||
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, dumpable, 0); errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetPdeathsig(sig syscall.Signal) error {
|
|
||||||
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(sig), 0); errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -14,8 +14,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/seccomp"
|
"git.gensokyo.uk/security/fortify/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -139,7 +137,7 @@ func (p *Container) Start() error {
|
|||||||
if p.CommandContext != nil {
|
if p.CommandContext != nil {
|
||||||
p.cmd = p.CommandContext(ctx)
|
p.cmd = p.CommandContext(ctx)
|
||||||
} else {
|
} else {
|
||||||
p.cmd = exec.CommandContext(ctx, internal.MustExecutable())
|
p.cmd = exec.CommandContext(ctx, MustExecutable())
|
||||||
p.cmd.Args = []string{"init"}
|
p.cmd.Args = []string{"init"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +164,7 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// place setup pipe before user supplied extra files, this is later restored by init
|
// place setup pipe before user supplied extra files, this is later restored by init
|
||||||
if fd, e, err := proc.Setup(&p.cmd.ExtraFiles); err != nil {
|
if fd, e, err := proc.Setup(&p.cmd.ExtraFiles); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
"cannot create shim setup pipe:")
|
"cannot create shim setup pipe:")
|
||||||
} else {
|
} else {
|
||||||
p.setup = e
|
p.setup = e
|
||||||
@ -174,9 +172,9 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...)
|
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...)
|
||||||
|
|
||||||
fmsg.Verbose("starting container init")
|
msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -187,7 +185,7 @@ func (p *Container) Serve() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.Path != "" && !path.IsAbs(p.Path) {
|
if p.Path != "" && !path.IsAbs(p.Path) {
|
||||||
return fmsg.WrapError(syscall.EINVAL,
|
return msg.WrapErr(syscall.EINVAL,
|
||||||
fmt.Sprintf("invalid executable path %q", p.Path))
|
fmt.Sprintf("invalid executable path %q", p.Path))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,14 +193,14 @@ func (p *Container) Serve() error {
|
|||||||
if p.name == "" {
|
if p.name == "" {
|
||||||
p.Path = os.Getenv("SHELL")
|
p.Path = os.Getenv("SHELL")
|
||||||
if !path.IsAbs(p.Path) {
|
if !path.IsAbs(p.Path) {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
"no command specified and $SHELL is invalid")
|
"no command specified and $SHELL is invalid")
|
||||||
}
|
}
|
||||||
p.name = path.Base(p.Path)
|
p.name = path.Base(p.Path)
|
||||||
} else if path.IsAbs(p.name) {
|
} else if path.IsAbs(p.name) {
|
||||||
p.Path = p.name
|
p.Path = p.name
|
||||||
} else if v, err := exec.LookPath(p.name); err != nil {
|
} else if v, err := exec.LookPath(p.name); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
} else {
|
} else {
|
||||||
p.Path = v
|
p.Path = v
|
||||||
}
|
}
|
||||||
@ -216,7 +214,7 @@ func (p *Container) Serve() error {
|
|||||||
syscall.Getuid(),
|
syscall.Getuid(),
|
||||||
syscall.Getgid(),
|
syscall.Getgid(),
|
||||||
len(p.ExtraFiles),
|
len(p.ExtraFiles),
|
||||||
fmsg.Load(),
|
msg.IsVerbose(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,10 @@ import (
|
|||||||
func TestContainer(t *testing.T) {
|
func TestContainer(t *testing.T) {
|
||||||
{
|
{
|
||||||
oldVerbose := fmsg.Load()
|
oldVerbose := fmsg.Load()
|
||||||
fmsg.Store(true)
|
oldOutput := sandbox.GetOutput()
|
||||||
|
internal.InstallFmsg(true)
|
||||||
t.Cleanup(func() { fmsg.Store(oldVerbose) })
|
t.Cleanup(func() { fmsg.Store(oldVerbose) })
|
||||||
|
t.Cleanup(func() { sandbox.SetOutput(oldOutput) })
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -146,7 +148,8 @@ func TestHelperInit(t *testing.T) {
|
|||||||
if len(os.Args) != 5 || os.Args[4] != "init" {
|
if len(os.Args) != 5 || os.Args[4] != "init" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sandbox.Init(internal.Exit)
|
sandbox.SetOutput(fmsg.Output{})
|
||||||
|
sandbox.Init(fmsg.Prepare, internal.InstallFmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperCheckContainer(t *testing.T) {
|
func TestHelperCheckContainer(t *testing.T) {
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package internal
|
package sandbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -15,7 +13,7 @@ var (
|
|||||||
|
|
||||||
func copyExecutable() {
|
func copyExecutable() {
|
||||||
if name, err := os.Executable(); err != nil {
|
if name, err := os.Executable(); err != nil {
|
||||||
fmsg.BeforeExit()
|
msg.BeforeExit()
|
||||||
log.Fatalf("cannot read executable path: %v", err)
|
log.Fatalf("cannot read executable path: %v", err)
|
||||||
} else {
|
} else {
|
||||||
executable = name
|
executable = name
|
@ -1,15 +1,15 @@
|
|||||||
package internal_test
|
package sandbox_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecutable(t *testing.T) {
|
func TestExecutable(t *testing.T) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if got := internal.MustExecutable(); got != os.Args[0] {
|
if got := sandbox.MustExecutable(); got != os.Args[0] {
|
||||||
t.Errorf("MustExecutable: %q, want %q",
|
t.Errorf("MustExecutable: %q, want %q",
|
||||||
got, os.Args[0])
|
got, os.Args[0])
|
||||||
}
|
}
|
@ -14,8 +14,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/seccomp"
|
"git.gensokyo.uk/security/fortify/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,9 +38,9 @@ type initParams struct {
|
|||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(exit func(code int)) {
|
func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
fmsg.Prepare("init")
|
prepare("init")
|
||||||
|
|
||||||
if os.Getpid() != 1 {
|
if os.Getpid() != 1 {
|
||||||
log.Fatal("this process must run as pid 1")
|
log.Fatal("this process must run as pid 1")
|
||||||
@ -72,14 +70,14 @@ func Init(exit func(code int)) {
|
|||||||
log.Fatal("invalid setup parameters")
|
log.Fatal("invalid setup parameters")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal.InstallFmsg(params.Verbose)
|
setVerbose(params.Verbose)
|
||||||
fmsg.Verbose("received setup parameters")
|
msg.Verbose("received setup parameters")
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
offsetSetup = int(setupFile.Fd() + 1)
|
offsetSetup = int(setupFile.Fd() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write uid/gid map here so parent does not need to set dumpable
|
// write uid/gid map here so parent does not need to set dumpable
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_USER); err != nil {
|
if err := SetDumpable(SUID_DUMP_USER); err != nil {
|
||||||
log.Fatalf("cannot set SUID_DUMP_USER: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_USER: %s", err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile("/proc/self/uid_map",
|
if err := os.WriteFile("/proc/self/uid_map",
|
||||||
@ -97,7 +95,7 @@ func Init(exit func(code int)) {
|
|||||||
0); err != nil {
|
0); err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
|
if err := SetDumpable(SUID_DUMP_DISABLE); err != nil {
|
||||||
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,11 +144,12 @@ func Init(exit func(code int)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, op := range *params.Ops {
|
for i, op := range *params.Ops {
|
||||||
fmsg.Verbosef("mounting %s", op)
|
msg.Verbosef("mounting %s", op)
|
||||||
if err := op.apply(¶ms.InitParams); err != nil {
|
if err := op.apply(¶ms.InitParams); err != nil {
|
||||||
fmsg.PrintBaseError(err,
|
msg.PrintBaseErr(err,
|
||||||
fmt.Sprintf("cannot apply op %d:", i))
|
fmt.Sprintf("cannot apply op %d:", i))
|
||||||
exit(1)
|
msg.BeforeExit()
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +168,7 @@ func Init(exit func(code int)) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var fd int
|
var fd int
|
||||||
if err := internal.IgnoringEINTR(func() (err error) {
|
if err := IgnoringEINTR(func() (err error) {
|
||||||
fd, err = syscall.Open("/", syscall.O_DIRECTORY|syscall.O_RDONLY, 0)
|
fd, err = syscall.Open("/", syscall.O_DIRECTORY|syscall.O_RDONLY, 0)
|
||||||
return
|
return
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@ -234,7 +233,7 @@ func Init(exit func(code int)) {
|
|||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
fmsg.Suspend()
|
msg.Suspend()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
close setup pipe
|
close setup pipe
|
||||||
@ -295,16 +294,17 @@ func Init(exit func(code int)) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case s := <-sig:
|
case s := <-sig:
|
||||||
if fmsg.Resume() {
|
if msg.Resume() {
|
||||||
fmsg.Verbosef("terminating on %s after process start", s.String())
|
msg.Verbosef("terminating on %s after process start", s.String())
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbosef("terminating on %s", s.String())
|
msg.Verbosef("terminating on %s", s.String())
|
||||||
}
|
}
|
||||||
exit(0)
|
msg.BeforeExit()
|
||||||
|
os.Exit(0)
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
// initial process exited, output is most likely available again
|
// initial process exited, output is most likely available again
|
||||||
fmsg.Resume()
|
msg.Resume()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case w.wstatus.Exited():
|
case w.wstatus.Exited():
|
||||||
@ -321,18 +321,22 @@ func Init(exit func(code int)) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
exit(r)
|
msg.BeforeExit()
|
||||||
|
os.Exit(r)
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
log.Println("timeout exceeded waiting for lingering processes")
|
log.Println("timeout exceeded waiting for lingering processes")
|
||||||
exit(r)
|
msg.BeforeExit()
|
||||||
|
os.Exit(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
// TryArgv0 calls [Init] if the last element of argv0 is "init".
|
||||||
func TryArgv0() {
|
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
|
||||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
|
if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
|
||||||
Init(internal.Exit)
|
msg = v
|
||||||
internal.Exit(0)
|
Init(prepare, setVerbose)
|
||||||
|
msg.BeforeExit()
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -30,34 +28,34 @@ func bindMount(src, dest string, flags int) error {
|
|||||||
if flags&BindOptional != 0 {
|
if flags&BindOptional != 0 {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return fmsg.WrapError(err,
|
return msg.WrapErr(err,
|
||||||
fmt.Sprintf("path %q does not exist", src))
|
fmt.Sprintf("path %q does not exist", src))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
} else {
|
} else {
|
||||||
source = toHost(rp)
|
source = toHost(rp)
|
||||||
}
|
}
|
||||||
} else if flags&BindOptional != 0 {
|
} else if flags&BindOptional != 0 {
|
||||||
return fmsg.WrapError(syscall.EINVAL,
|
return msg.WrapErr(syscall.EINVAL,
|
||||||
"flag source excludes optional")
|
"flag source excludes optional")
|
||||||
} else {
|
} else {
|
||||||
source = toHost(src)
|
source = toHost(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi, err := os.Stat(source); err != nil {
|
if fi, err := os.Stat(source); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
} else if fi.IsDir() {
|
} else if fi.IsDir() {
|
||||||
if err = os.MkdirAll(target, 0755); err != nil {
|
if err = os.MkdirAll(target, 0755); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot create directory %q:", dest))
|
fmt.Sprintf("cannot create directory %q:", dest))
|
||||||
}
|
}
|
||||||
} else if err = ensureFile(target, 0444); err != nil {
|
} else if err = ensureFile(target, 0444); err != nil {
|
||||||
if errors.Is(err, syscall.EISDIR) {
|
if errors.Is(err, syscall.EISDIR) {
|
||||||
return fmsg.WrapError(err,
|
return msg.WrapErr(err,
|
||||||
fmt.Sprintf("path %q is a directory", dest))
|
fmt.Sprintf("path %q is a directory", dest))
|
||||||
}
|
}
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot create %q:", dest))
|
fmt.Sprintf("cannot create %q:", dest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,14 +69,14 @@ func bindMount(src, dest string, flags int) error {
|
|||||||
if flags&BindDevices == 0 {
|
if flags&BindDevices == 0 {
|
||||||
mf |= syscall.MS_NODEV
|
mf |= syscall.MS_NODEV
|
||||||
}
|
}
|
||||||
if fmsg.Load() {
|
if msg.IsVerbose() {
|
||||||
if strings.TrimPrefix(source, hostPath) == strings.TrimPrefix(target, sysrootPath) {
|
if strings.TrimPrefix(source, hostPath) == strings.TrimPrefix(target, sysrootPath) {
|
||||||
fmsg.Verbosef("resolved %q flags %#x", target, mf)
|
msg.Verbosef("resolved %q flags %#x", target, mf)
|
||||||
} else {
|
} else {
|
||||||
fmsg.Verbosef("resolved %q on %q flags %#x", source, target, mf)
|
msg.Verbosef("resolved %q on %q flags %#x", source, target, mf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmsg.WrapErrorSuffix(syscall.Mount(source, target, "", mf, ""),
|
return wrapErrSuffix(syscall.Mount(source, target, "", mf, ""),
|
||||||
fmt.Sprintf("cannot bind %q on %q:", src, dest))
|
fmt.Sprintf("cannot bind %q on %q:", src, dest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +89,7 @@ func mountTmpfs(fsname, name string, size int, perm os.FileMode) error {
|
|||||||
if size > 0 {
|
if size > 0 {
|
||||||
opt += fmt.Sprintf(",size=%d", size)
|
opt += fmt.Sprintf(",size=%d", size)
|
||||||
}
|
}
|
||||||
return fmsg.WrapErrorSuffix(syscall.Mount(fsname, target, "tmpfs",
|
return wrapErrSuffix(syscall.Mount(fsname, target, "tmpfs",
|
||||||
syscall.MS_NOSUID|syscall.MS_NODEV, opt),
|
syscall.MS_NOSUID|syscall.MS_NODEV, opt),
|
||||||
fmt.Sprintf("cannot mount tmpfs on %q:", name))
|
fmt.Sprintf("cannot mount tmpfs on %q:", name))
|
||||||
}
|
}
|
||||||
|
43
internal/sandbox/msg.go
Normal file
43
internal/sandbox/msg.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Msg interface {
|
||||||
|
IsVerbose() bool
|
||||||
|
Verbose(v ...any)
|
||||||
|
Verbosef(format string, v ...any)
|
||||||
|
WrapErr(err error, a ...any) error
|
||||||
|
PrintBaseErr(err error, fallback string)
|
||||||
|
|
||||||
|
Suspend()
|
||||||
|
Resume() bool
|
||||||
|
|
||||||
|
BeforeExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultMsg struct{ inactive atomic.Bool }
|
||||||
|
|
||||||
|
func (msg *DefaultMsg) IsVerbose() bool { return true }
|
||||||
|
func (msg *DefaultMsg) Verbose(v ...any) {
|
||||||
|
if !msg.inactive.Load() {
|
||||||
|
log.Println(v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (msg *DefaultMsg) Verbosef(format string, v ...any) {
|
||||||
|
if !msg.inactive.Load() {
|
||||||
|
log.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *DefaultMsg) WrapErr(err error, a ...any) error {
|
||||||
|
log.Println(a...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (msg *DefaultMsg) PrintBaseErr(err error, fallback string) { log.Println(fallback, err) }
|
||||||
|
|
||||||
|
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
|
||||||
|
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
|
||||||
|
func (msg *DefaultMsg) BeforeExit() {}
|
19
internal/sandbox/output.go
Normal file
19
internal/sandbox/output.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package sandbox
|
||||||
|
|
||||||
|
var msg Msg = new(DefaultMsg)
|
||||||
|
|
||||||
|
func GetOutput() Msg { return msg }
|
||||||
|
func SetOutput(v Msg) {
|
||||||
|
if v == nil {
|
||||||
|
msg = new(DefaultMsg)
|
||||||
|
} else {
|
||||||
|
msg = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErrSuffix(err error, a ...any) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return msg.WrapErr(err, append(a, err)...)
|
||||||
|
}
|
@ -7,8 +7,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -43,7 +41,7 @@ func realpathHost(name string) (string, error) {
|
|||||||
if !path.IsAbs(rp) {
|
if !path.IsAbs(rp) {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
fmsg.Verbosef("path %q resolves to %q", name, rp)
|
msg.Verbosef("path %q resolves to %q", name, rp)
|
||||||
return rp, nil
|
return rp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { gob.Register(new(BindMount)) }
|
func init() { gob.Register(new(BindMount)) }
|
||||||
@ -23,7 +21,7 @@ type BindMount struct {
|
|||||||
|
|
||||||
func (b *BindMount) apply(*InitParams) error {
|
func (b *BindMount) apply(*InitParams) error {
|
||||||
if !path.IsAbs(b.Source) || !path.IsAbs(b.Target) {
|
if !path.IsAbs(b.Source) || !path.IsAbs(b.Target) {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
"path is not absolute")
|
"path is not absolute")
|
||||||
}
|
}
|
||||||
return bindMount(b.Source, b.Target, b.Flags)
|
return bindMount(b.Source, b.Target, b.Flags)
|
||||||
@ -50,15 +48,15 @@ type MountProc struct {
|
|||||||
|
|
||||||
func (p *MountProc) apply(*InitParams) error {
|
func (p *MountProc) apply(*InitParams) error {
|
||||||
if !path.IsAbs(p.Path) {
|
if !path.IsAbs(p.Path) {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
fmt.Sprintf("path %q is not absolute", p.Path))
|
fmt.Sprintf("path %q is not absolute", p.Path))
|
||||||
}
|
}
|
||||||
|
|
||||||
target := toSysroot(p.Path)
|
target := toSysroot(p.Path)
|
||||||
if err := os.MkdirAll(target, 0755); err != nil {
|
if err := os.MkdirAll(target, 0755); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
}
|
}
|
||||||
return fmsg.WrapErrorSuffix(syscall.Mount("proc", target, "proc",
|
return wrapErrSuffix(syscall.Mount("proc", target, "proc",
|
||||||
syscall.MS_NOSUID|syscall.MS_NOEXEC|syscall.MS_NODEV, ""),
|
syscall.MS_NOSUID|syscall.MS_NOEXEC|syscall.MS_NODEV, ""),
|
||||||
fmt.Sprintf("cannot mount proc on %q:", p.Path))
|
fmt.Sprintf("cannot mount proc on %q:", p.Path))
|
||||||
}
|
}
|
||||||
@ -72,7 +70,7 @@ type MountDev struct {
|
|||||||
|
|
||||||
func (d *MountDev) apply(params *InitParams) error {
|
func (d *MountDev) apply(params *InitParams) error {
|
||||||
if !path.IsAbs(d.Path) {
|
if !path.IsAbs(d.Path) {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
fmt.Sprintf("path %q is not absolute", d.Path))
|
fmt.Sprintf("path %q is not absolute", d.Path))
|
||||||
}
|
}
|
||||||
target := toSysroot(d.Path)
|
target := toSysroot(d.Path)
|
||||||
@ -94,7 +92,7 @@ func (d *MountDev) apply(params *InitParams) error {
|
|||||||
"/proc/self/fd/"+string(rune(i+'0')),
|
"/proc/self/fd/"+string(rune(i+'0')),
|
||||||
path.Join(target, name),
|
path.Join(target, name),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pair := range [][2]string{
|
for _, pair := range [][2]string{
|
||||||
@ -103,21 +101,21 @@ func (d *MountDev) apply(params *InitParams) error {
|
|||||||
{"pts/ptmx", "ptmx"},
|
{"pts/ptmx", "ptmx"},
|
||||||
} {
|
} {
|
||||||
if err := os.Symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
if err := os.Symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
devPtsPath := path.Join(target, "pts")
|
devPtsPath := path.Join(target, "pts")
|
||||||
for _, name := range []string{path.Join(target, "shm"), devPtsPath} {
|
for _, name := range []string{path.Join(target, "shm"), devPtsPath} {
|
||||||
if err := os.Mkdir(name, 0755); err != nil {
|
if err := os.Mkdir(name, 0755); err != nil {
|
||||||
return fmsg.WrapError(err, err.Error())
|
return msg.WrapErr(err, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syscall.Mount("devpts", devPtsPath, "devpts",
|
if err := syscall.Mount("devpts", devPtsPath, "devpts",
|
||||||
syscall.MS_NOSUID|syscall.MS_NOEXEC,
|
syscall.MS_NOSUID|syscall.MS_NOEXEC,
|
||||||
"newinstance,ptmxmode=0666,mode=620"); err != nil {
|
"newinstance,ptmxmode=0666,mode=620"); err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot mount devpts on %q:", devPtsPath))
|
fmt.Sprintf("cannot mount devpts on %q:", devPtsPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +162,11 @@ type MountTmpfs struct {
|
|||||||
|
|
||||||
func (t *MountTmpfs) apply(*InitParams) error {
|
func (t *MountTmpfs) apply(*InitParams) error {
|
||||||
if !path.IsAbs(t.Path) {
|
if !path.IsAbs(t.Path) {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
fmt.Sprintf("path %q is not absolute", t.Path))
|
fmt.Sprintf("path %q is not absolute", t.Path))
|
||||||
}
|
}
|
||||||
if t.Size < 0 || t.Size > math.MaxUint>>1 {
|
if t.Size < 0 || t.Size > math.MaxUint>>1 {
|
||||||
return fmsg.WrapError(syscall.EBADE,
|
return msg.WrapErr(syscall.EBADE,
|
||||||
fmt.Sprintf("size %d out of bounds", t.Size))
|
fmt.Sprintf("size %d out of bounds", t.Size))
|
||||||
}
|
}
|
||||||
return mountTmpfs("tmpfs", t.Path, t.Size, t.Perm)
|
return mountTmpfs("tmpfs", t.Path, t.Size, t.Perm)
|
||||||
|
@ -1,7 +1,29 @@
|
|||||||
package internal
|
package sandbox
|
||||||
|
|
||||||
import "syscall"
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
SUID_DUMP_DISABLE = iota
|
||||||
|
SUID_DUMP_USER
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDumpable(dumpable uintptr) error {
|
||||||
|
// linux/sched/coredump.h
|
||||||
|
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, dumpable, 0); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetPdeathsig(sig syscall.Signal) error {
|
||||||
|
if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(sig), 0); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IgnoringEINTR makes a function call and repeats it if it returns an
|
// IgnoringEINTR makes a function call and repeats it if it returns an
|
||||||
// EINTR error. This appears to be required even though we install all
|
// EINTR error. This appears to be required even though we install all
|
||||||
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
@ -15,6 +15,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Std implements System using the standard library.
|
// Std implements System using the standard library.
|
||||||
@ -34,7 +35,7 @@ func (s *Std) Geteuid() int { return os.Geteuid(
|
|||||||
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||||
func (s *Std) TempDir() string { return os.TempDir() }
|
func (s *Std) TempDir() string { return os.TempDir() }
|
||||||
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
||||||
func (s *Std) MustExecutable() string { return internal.MustExecutable() }
|
func (s *Std) MustExecutable() string { return sandbox.MustExecutable() }
|
||||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
||||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
||||||
|
4
main.go
4
main.go
@ -42,10 +42,10 @@ var std sys.State = new(sys.Std)
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
// early init path, skips root check and duplicate PR_SET_DUMPABLE
|
||||||
sandbox.TryArgv0()
|
sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg)
|
||||||
init0.TryArgv0()
|
init0.TryArgv0()
|
||||||
|
|
||||||
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
|
if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil {
|
||||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
||||||
// not fatal: this program runs as the privileged user
|
// not fatal: this program runs as the privileged user
|
||||||
}
|
}
|
||||||
|
@ -35,24 +35,24 @@ type ACL struct {
|
|||||||
func (a *ACL) Type() Enablement { return a.et }
|
func (a *ACL) Type() Enablement { return a.et }
|
||||||
|
|
||||||
func (a *ACL) apply(sys *I) error {
|
func (a *ACL) apply(sys *I) error {
|
||||||
sys.println("applying ACL", a)
|
msg.Verbose("applying ACL", a)
|
||||||
return sys.wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
|
return wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
|
||||||
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(a) {
|
if ec.hasType(a) {
|
||||||
sys.println("stripping ACL", a)
|
msg.Verbose("stripping ACL", a)
|
||||||
err := acl.Update(a.path, sys.uid)
|
err := acl.Update(a.path, sys.uid)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
// the ACL is effectively stripped if the file no longer exists
|
// the ACL is effectively stripped if the file no longer exists
|
||||||
sys.printf("target of ACL %s no longer exists", a)
|
msg.Verbosef("target of ACL %s no longer exists", a)
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
||||||
} else {
|
} else {
|
||||||
sys.println("skipping ACL", a)
|
msg.Verbose("skipping ACL", a)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
|
|
||||||
// session bus is mandatory
|
// session bus is mandatory
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return nil, sys.wrapErr(ErrDBusConfig,
|
return nil, msg.WrapErr(ErrDBusConfig,
|
||||||
"attempted to seal message bus proxy without session bus config")
|
"attempted to seal message bus proxy without session bus config")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,12 +48,12 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
d.proxy = dbus.New(sessionBus, systemBus)
|
d.proxy = dbus.New(sessionBus, systemBus)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if sys.IsVerbose() && d.proxy.Sealed() {
|
if msg.IsVerbose() && d.proxy.Sealed() {
|
||||||
sys.println("sealed session proxy", session.Args(sessionBus))
|
msg.Verbose("sealed session proxy", session.Args(sessionBus))
|
||||||
if system != nil {
|
if system != nil {
|
||||||
sys.println("sealed system proxy", system.Args(systemBus))
|
msg.Verbose("sealed system proxy", system.Args(systemBus))
|
||||||
}
|
}
|
||||||
sys.println("message bus proxy final args:", d.proxy)
|
msg.Verbose("message bus proxy final args:", d.proxy)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
|
|||||||
|
|
||||||
// seal dbus proxy
|
// seal dbus proxy
|
||||||
d.out = &scanToFmsg{msg: new(strings.Builder)}
|
d.out = &scanToFmsg{msg: new(strings.Builder)}
|
||||||
return d.out.Dump, sys.wrapErrSuffix(d.proxy.Seal(session, system),
|
return d.out.Dump, wrapErrSuffix(d.proxy.Seal(session, system),
|
||||||
"cannot seal message bus proxy:")
|
"cannot seal message bus proxy:")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,32 +77,32 @@ type DBus struct {
|
|||||||
func (d *DBus) Type() Enablement { return Process }
|
func (d *DBus) Type() Enablement { return Process }
|
||||||
|
|
||||||
func (d *DBus) apply(sys *I) error {
|
func (d *DBus) apply(sys *I) error {
|
||||||
sys.printf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
msg.Verbosef("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
|
||||||
if d.system {
|
if d.system {
|
||||||
sys.printf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
msg.Verbosef("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// this starts the process and blocks until ready
|
// this starts the process and blocks until ready
|
||||||
if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
|
if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
|
||||||
d.out.Dump()
|
d.out.Dump()
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
"cannot start message bus proxy:")
|
"cannot start message bus proxy:")
|
||||||
}
|
}
|
||||||
sys.println("starting message bus proxy", d.proxy)
|
msg.Verbose("starting message bus proxy", d.proxy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) revert(sys *I, _ *Criteria) error {
|
func (d *DBus) revert(*I, *Criteria) error {
|
||||||
// criteria ignored here since dbus is always process-scoped
|
// criteria ignored here since dbus is always process-scoped
|
||||||
sys.println("terminating message bus proxy")
|
msg.Verbose("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
d.proxy.Close()
|
||||||
defer sys.println("message bus proxy exit")
|
defer msg.Verbose("message bus proxy exit")
|
||||||
err := d.proxy.Wait()
|
err := d.proxy.Wait()
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
sys.println("message bus proxy canceled upstream")
|
msg.Verbose("message bus proxy canceled upstream")
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return sys.wrapErrSuffix(err, "message bus proxy error:")
|
return wrapErrSuffix(err, "message bus proxy error:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Is(o Op) bool {
|
func (d *DBus) Is(o Op) bool {
|
||||||
|
@ -25,19 +25,19 @@ type Hardlink struct {
|
|||||||
|
|
||||||
func (l *Hardlink) Type() Enablement { return l.et }
|
func (l *Hardlink) Type() Enablement { return l.et }
|
||||||
|
|
||||||
func (l *Hardlink) apply(sys *I) error {
|
func (l *Hardlink) apply(*I) error {
|
||||||
sys.println("linking", l)
|
msg.Verbose("linking", l)
|
||||||
return sys.wrapErrSuffix(os.Link(l.src, l.dst),
|
return wrapErrSuffix(os.Link(l.src, l.dst),
|
||||||
fmt.Sprintf("cannot link %q:", l.dst))
|
fmt.Sprintf("cannot link %q:", l.dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Hardlink) revert(sys *I, ec *Criteria) error {
|
func (l *Hardlink) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(l) {
|
if ec.hasType(l) {
|
||||||
sys.printf("removing hard link %q", l.dst)
|
msg.Verbosef("removing hard link %q", l.dst)
|
||||||
return sys.wrapErrSuffix(os.Remove(l.dst),
|
return wrapErrSuffix(os.Remove(l.dst),
|
||||||
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
fmt.Sprintf("cannot remove hard link %q:", l.dst))
|
||||||
} else {
|
} else {
|
||||||
sys.printf("skipping hard link %q", l.dst)
|
msg.Verbosef("skipping hard link %q", l.dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,33 +37,33 @@ func (m *Mkdir) Type() Enablement {
|
|||||||
return m.et
|
return m.et
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) apply(sys *I) error {
|
func (m *Mkdir) apply(*I) error {
|
||||||
sys.println("ensuring directory", m)
|
msg.Verbose("ensuring directory", m)
|
||||||
|
|
||||||
// create directory
|
// create directory
|
||||||
err := os.Mkdir(m.path, m.perm)
|
err := os.Mkdir(m.path, m.perm)
|
||||||
if !errors.Is(err, os.ErrExist) {
|
if !errors.Is(err, os.ErrExist) {
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot create directory %q:", m.path))
|
fmt.Sprintf("cannot create directory %q:", m.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// directory exists, ensure mode
|
// directory exists, ensure mode
|
||||||
return sys.wrapErrSuffix(os.Chmod(m.path, m.perm),
|
return wrapErrSuffix(os.Chmod(m.path, m.perm),
|
||||||
fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
|
fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mkdir) revert(sys *I, ec *Criteria) error {
|
func (m *Mkdir) revert(_ *I, ec *Criteria) error {
|
||||||
if !m.ephemeral {
|
if !m.ephemeral {
|
||||||
// skip non-ephemeral dir and do not log anything
|
// skip non-ephemeral dir and do not log anything
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ec.hasType(m) {
|
if ec.hasType(m) {
|
||||||
sys.println("destroying ephemeral directory", m)
|
msg.Verbose("destroying ephemeral directory", m)
|
||||||
return sys.wrapErrSuffix(os.Remove(m.path),
|
return wrapErrSuffix(os.Remove(m.path),
|
||||||
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
||||||
} else {
|
} else {
|
||||||
sys.println("skipping ephemeral directory", m)
|
msg.Verbose("skipping ephemeral directory", m)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
system/op.go
22
system/op.go
@ -60,10 +60,6 @@ func TypeString(e Enablement) string {
|
|||||||
func New(uid int) (sys *I) {
|
func New(uid int) (sys *I) {
|
||||||
sys = new(I)
|
sys = new(I)
|
||||||
sys.uid = uid
|
sys.uid = uid
|
||||||
sys.IsVerbose = func() bool { return false }
|
|
||||||
sys.Verbose = func(...any) {}
|
|
||||||
sys.Verbosef = func(string, ...any) {}
|
|
||||||
sys.WrapErr = func(err error, _ ...any) error { return err }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,27 +69,13 @@ type I struct {
|
|||||||
ops []Op
|
ops []Op
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
IsVerbose func() bool
|
|
||||||
Verbose func(v ...any)
|
|
||||||
Verbosef func(format string, v ...any)
|
|
||||||
WrapErr func(err error, a ...any) error
|
|
||||||
|
|
||||||
// whether sys has been reverted
|
// whether sys has been reverted
|
||||||
state bool
|
state bool
|
||||||
|
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sys *I) UID() int { return sys.uid }
|
func (sys *I) UID() int { return sys.uid }
|
||||||
func (sys *I) println(v ...any) { sys.Verbose(v...) }
|
|
||||||
func (sys *I) printf(format string, v ...any) { sys.Verbosef(format, v...) }
|
|
||||||
func (sys *I) wrapErr(err error, a ...any) error { return sys.WrapErr(err, a...) }
|
|
||||||
func (sys *I) wrapErrSuffix(err error, a ...any) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return sys.wrapErr(err, append(a, err)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal returns whether all [Op] instances held by v is identical to that of sys.
|
// Equal returns whether all [Op] instances held by v is identical to that of sys.
|
||||||
func (sys *I) Equal(v *I) bool {
|
func (sys *I) Equal(v *I) bool {
|
||||||
@ -127,7 +109,7 @@ func (sys *I) Commit(ctx context.Context) error {
|
|||||||
// sp is set to nil when all ops are applied
|
// sp is set to nil when all ops are applied
|
||||||
if sp != nil {
|
if sp != nil {
|
||||||
// rollback partial commit
|
// rollback partial commit
|
||||||
sys.printf("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
|
||||||
if err := sp.Revert(&Criteria{nil}); err != nil {
|
if err := sp.Revert(&Criteria{nil}); err != nil {
|
||||||
log.Println("errors returned reverting partial commit:", err)
|
log.Println("errors returned reverting partial commit:", err)
|
||||||
}
|
}
|
||||||
|
20
system/output.go
Normal file
20
system/output.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import "git.gensokyo.uk/security/fortify/internal/sandbox"
|
||||||
|
|
||||||
|
var msg sandbox.Msg = new(sandbox.DefaultMsg)
|
||||||
|
|
||||||
|
func SetOutput(v sandbox.Msg) {
|
||||||
|
if v == nil {
|
||||||
|
msg = new(sandbox.DefaultMsg)
|
||||||
|
} else {
|
||||||
|
msg = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapErrSuffix(err error, a ...any) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return msg.WrapErr(err, append(a, err)...)
|
||||||
|
}
|
@ -31,8 +31,8 @@ type Tmpfile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tmpfile) Type() Enablement { return Process }
|
func (t *Tmpfile) Type() Enablement { return Process }
|
||||||
func (t *Tmpfile) apply(sys *I) error {
|
func (t *Tmpfile) apply(*I) error {
|
||||||
sys.println("copying", t)
|
msg.Verbose("copying", t)
|
||||||
|
|
||||||
if t.payload == nil {
|
if t.payload == nil {
|
||||||
// this is a misuse of the API; do not return an error message
|
// this is a misuse of the API; do not return an error message
|
||||||
@ -40,25 +40,25 @@ func (t *Tmpfile) apply(sys *I) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b, err := os.Stat(t.src); err != nil {
|
if b, err := os.Stat(t.src); err != nil {
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot stat %q:", t.src))
|
fmt.Sprintf("cannot stat %q:", t.src))
|
||||||
} else {
|
} else {
|
||||||
if b.IsDir() {
|
if b.IsDir() {
|
||||||
return sys.wrapErrSuffix(syscall.EISDIR,
|
return wrapErrSuffix(syscall.EISDIR,
|
||||||
fmt.Sprintf("%q is a directory", t.src))
|
fmt.Sprintf("%q is a directory", t.src))
|
||||||
}
|
}
|
||||||
if s := b.Size(); s > t.n {
|
if s := b.Size(); s > t.n {
|
||||||
return sys.wrapErrSuffix(syscall.ENOMEM,
|
return wrapErrSuffix(syscall.ENOMEM,
|
||||||
fmt.Sprintf("file %q is too long: %d > %d",
|
fmt.Sprintf("file %q is too long: %d > %d",
|
||||||
t.src, s, t.n))
|
t.src, s, t.n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, err := os.Open(t.src); err != nil {
|
if f, err := os.Open(t.src); err != nil {
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot open %q:", t.src))
|
fmt.Sprintf("cannot open %q:", t.src))
|
||||||
} else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
|
} else if _, err = io.CopyN(t.buf, f, t.n); err != nil {
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot read from %q:", t.src))
|
fmt.Sprintf("cannot read from %q:", t.src))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,35 +46,35 @@ func (w *Wayland) apply(sys *I) error {
|
|||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
err = os.ErrNotExist
|
err = os.ErrNotExist
|
||||||
}
|
}
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot attach to wayland on %q:", w.src))
|
fmt.Sprintf("cannot attach to wayland on %q:", w.src))
|
||||||
} else {
|
} else {
|
||||||
sys.printf("wayland attached on %q", w.src)
|
msg.Verbosef("wayland attached on %q", w.src)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
|
if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
|
||||||
return sys.wrapErrSuffix(err,
|
return wrapErrSuffix(err,
|
||||||
fmt.Sprintf("cannot bind to socket on %q:", w.dst))
|
fmt.Sprintf("cannot bind to socket on %q:", w.dst))
|
||||||
} else {
|
} else {
|
||||||
*w.sync = sp
|
*w.sync = sp
|
||||||
sys.printf("wayland listening on %q", w.dst)
|
msg.Verbosef("wayland listening on %q", w.dst)
|
||||||
return sys.wrapErrSuffix(errors.Join(os.Chmod(w.dst, 0), acl.Update(w.dst, sys.uid, acl.Read, acl.Write, acl.Execute)),
|
return wrapErrSuffix(errors.Join(os.Chmod(w.dst, 0), acl.Update(w.dst, sys.uid, acl.Read, acl.Write, acl.Execute)),
|
||||||
fmt.Sprintf("cannot chmod socket on %q:", w.dst))
|
fmt.Sprintf("cannot chmod socket on %q:", w.dst))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wayland) revert(sys *I, ec *Criteria) error {
|
func (w *Wayland) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(w) {
|
if ec.hasType(w) {
|
||||||
sys.printf("removing wayland socket on %q", w.dst)
|
msg.Verbosef("removing wayland socket on %q", w.dst)
|
||||||
if err := os.Remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
|
if err := os.Remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.printf("detaching from wayland on %q", w.src)
|
msg.Verbosef("detaching from wayland on %q", w.src)
|
||||||
return sys.wrapErrSuffix(w.conn.Close(),
|
return wrapErrSuffix(w.conn.Close(),
|
||||||
fmt.Sprintf("cannot detach from wayland on %q:", w.src))
|
fmt.Sprintf("cannot detach from wayland on %q:", w.src))
|
||||||
} else {
|
} else {
|
||||||
sys.printf("skipping wayland cleanup on %q", w.dst)
|
msg.Verbosef("skipping wayland cleanup on %q", w.dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,19 +22,19 @@ func (x XHost) Type() Enablement {
|
|||||||
return EX11
|
return EX11
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) apply(sys *I) error {
|
func (x XHost) apply(*I) error {
|
||||||
sys.printf("inserting entry %s to X11", x)
|
msg.Verbosef("inserting entry %s to X11", x)
|
||||||
return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x XHost) revert(sys *I, ec *Criteria) error {
|
func (x XHost) revert(_ *I, ec *Criteria) error {
|
||||||
if ec.hasType(x) {
|
if ec.hasType(x) {
|
||||||
sys.printf("deleting entry %s from X11", x)
|
msg.Verbosef("deleting entry %s from X11", x)
|
||||||
return sys.wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
return wrapErrSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
||||||
} else {
|
} else {
|
||||||
sys.printf("skipping entry %s in X11", x)
|
msg.Verbosef("skipping entry %s in X11", x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user