Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
d6cf736abf | |||
15011c4173 | |||
31b7ddd122 | |||
c460892cbd | |||
6309469e93 | |||
0d7c1a9a43 | |||
ae6f5ede19 | |||
807d511c8b | |||
2f4f21fb18 | |||
9967909460 | |||
c806f43881 | |||
584405f7cc | |||
50127ed5f9 | |||
b5eff27c40 | |||
74ba183256 | |||
f885dede9b | |||
e9a7cd526f | |||
12be7bc78e | |||
0ba8be659f | |||
022242a84a | |||
8aeb06f53c | |||
4036da3b5c | |||
986105958c | |||
ecdd4d8202 | |||
bdee0c3921 |
@ -19,7 +19,7 @@ type appInfo struct {
|
|||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
AppID int `json:"app_id"`
|
Identity int `json:"identity"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
@ -29,7 +29,7 @@ type appInfo struct {
|
|||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
Net bool `json:"net,omitempty"`
|
Net bool `json:"net,omitempty"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
Dev bool `json:"dev,omitempty"`
|
Device bool `json:"dev,omitempty"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
Tty bool `json:"tty,omitempty"`
|
Tty bool `json:"tty,omitempty"`
|
||||||
// passed through to [fst.Config]
|
// passed through to [fst.Config]
|
||||||
@ -65,23 +65,32 @@ type appInfo struct {
|
|||||||
func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *fst.Config {
|
func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *fst.Config {
|
||||||
config := &fst.Config{
|
config := &fst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: argv[0],
|
Path: argv[0],
|
||||||
Args: argv,
|
Args: argv,
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: app.AppID,
|
Enablements: app.Enablements,
|
||||||
Groups: app.Groups,
|
|
||||||
|
SystemBus: app.SystemBus,
|
||||||
|
SessionBus: app.SessionBus,
|
||||||
|
DirectWayland: app.DirectWayland,
|
||||||
|
|
||||||
Username: "fortify",
|
Username: "fortify",
|
||||||
Inner: path.Join("/data/data", app.ID),
|
Shell: shellPath,
|
||||||
Outer: pathSet.homeDir,
|
Data: pathSet.homeDir,
|
||||||
Sandbox: &fst.SandboxConfig{
|
Dir: path.Join("/data/data", app.ID),
|
||||||
|
|
||||||
|
Identity: app.Identity,
|
||||||
|
Groups: app.Groups,
|
||||||
|
|
||||||
|
Container: &fst.ContainerConfig{
|
||||||
Hostname: formatHostname(app.Name),
|
Hostname: formatHostname(app.Name),
|
||||||
Devel: app.Devel,
|
Devel: app.Devel,
|
||||||
Userns: app.Userns,
|
Userns: app.Userns,
|
||||||
Net: app.Net,
|
Net: app.Net,
|
||||||
Dev: app.Dev,
|
Device: app.Device,
|
||||||
Tty: app.Tty || flagDropShell,
|
Tty: app.Tty || flagDropShell,
|
||||||
MapRealUID: app.MapRealUID,
|
MapRealUID: app.MapRealUID,
|
||||||
DirectWayland: app.DirectWayland,
|
|
||||||
Filesystem: []*fst.FilesystemConfig{
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
|
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
|
||||||
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
|
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
|
||||||
@ -104,16 +113,12 @@ func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool
|
|||||||
{Path: dataHome, Execute: true},
|
{Path: dataHome, Execute: true},
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
},
|
},
|
||||||
SystemBus: app.SystemBus,
|
|
||||||
SessionBus: app.SessionBus,
|
|
||||||
Enablements: app.Enablements,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if app.Multiarch {
|
if app.Multiarch {
|
||||||
config.Confinement.Sandbox.Seccomp |= seccomp.FlagMultiarch
|
config.Container.Seccomp |= seccomp.FilterMultiarch
|
||||||
}
|
}
|
||||||
if app.Bluetooth {
|
if app.Bluetooth {
|
||||||
config.Confinement.Sandbox.Seccomp |= seccomp.FlagBluetooth
|
config.Container.Seccomp |= seccomp.FilterBluetooth
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
'',
|
'',
|
||||||
|
|
||||||
id ? name,
|
id ? name,
|
||||||
app_id ? throw "app_id is required",
|
identity ? throw "identity is required",
|
||||||
groups ? [ ],
|
groups ? [ ],
|
||||||
userns ? false,
|
userns ? false,
|
||||||
net ? true,
|
net ? true,
|
||||||
@ -147,7 +147,7 @@ let
|
|||||||
name
|
name
|
||||||
version
|
version
|
||||||
id
|
id
|
||||||
app_id
|
identity
|
||||||
launcher
|
launcher
|
||||||
groups
|
groups
|
||||||
userns
|
userns
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/command"
|
"git.gensokyo.uk/security/fortify/command"
|
||||||
"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/app"
|
"git.gensokyo.uk/security/fortify/internal/app/instance"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
@ -62,7 +62,7 @@ func main() {
|
|||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action")
|
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { app.ShimMain(); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
@ -157,11 +157,11 @@ func main() {
|
|||||||
return errSuccess
|
return errSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppID determines uid
|
// identity determines uid
|
||||||
if a.AppID != bundle.AppID {
|
if a.Identity != bundle.Identity {
|
||||||
cleanup()
|
cleanup()
|
||||||
log.Printf("package %q app id %d differs from installed %d",
|
log.Printf("package %q identity %d differs from installed %d",
|
||||||
pkgPath, bundle.AppID, a.AppID)
|
pkgPath, bundle.Identity, a.Identity)
|
||||||
return syscall.EBADE
|
return syscall.EBADE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ func main() {
|
|||||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
"--override-input nixpkgs path:/etc/nixpkgs " +
|
||||||
"path:" + a.NixGL + "#nixVulkanNvidia",
|
"path:" + a.NixGL + "#nixVulkanNvidia",
|
||||||
}, true, func(config *fst.Config) *fst.Config {
|
}, true, func(config *fst.Config) *fst.Config {
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
config.Container.Filesystem = append(config.Container.Filesystem, []*fst.FilesystemConfig{
|
||||||
{Src: "/etc/resolv.conf"},
|
{Src: "/etc/resolv.conf"},
|
||||||
{Src: "/sys/block"},
|
{Src: "/sys/block"},
|
||||||
{Src: "/sys/bus"},
|
{Src: "/sys/bus"},
|
||||||
@ -324,7 +324,7 @@ func main() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if a.GPU {
|
if a.GPU {
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem,
|
config.Container.Filesystem = append(config.Container.Filesystem,
|
||||||
&fst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(fst.Tmp, "nixGL")})
|
&fst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(fst.Tmp, "nixGL")})
|
||||||
appendGPUFilesystem(config)
|
appendGPUFilesystem(config)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func pathSetByApp(id string) *appPathSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendGPUFilesystem(config *fst.Config) {
|
func appendGPUFilesystem(config *fst.Config) {
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
config.Container.Filesystem = append(config.Container.Filesystem, []*fst.FilesystemConfig{
|
||||||
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
||||||
{Src: "/dev/dri", Device: true},
|
{Src: "/dev/dri", Device: true},
|
||||||
// mali
|
// mali
|
||||||
|
@ -6,23 +6,24 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/instance"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) {
|
func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) {
|
||||||
rs := new(fst.RunState)
|
rs := new(app.RunState)
|
||||||
a := app.MustNew(ctx, std)
|
a := instance.MustNew(instance.ISetuid, ctx, std)
|
||||||
|
|
||||||
|
var code int
|
||||||
if sa, err := a.Seal(config); err != nil {
|
if sa, err := a.Seal(config); err != nil {
|
||||||
fmsg.PrintBaseError(err, "cannot seal app:")
|
fmsg.PrintBaseError(err, "cannot seal app:")
|
||||||
rs.ExitCode = 1
|
code = 1
|
||||||
} else {
|
} else {
|
||||||
// this updates ExitCode
|
code = instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs))
|
||||||
app.PrintRunStateErr(rs, sa.Run(rs))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs.ExitCode != 0 {
|
if code != 0 {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
os.Exit(rs.ExitCode)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ buildPackage {
|
|||||||
name = "foot";
|
name = "foot";
|
||||||
inherit (foot) version;
|
inherit (foot) version;
|
||||||
|
|
||||||
app_id = 2;
|
identity = 2;
|
||||||
id = "org.codeberg.dnkl.foot";
|
id = "org.codeberg.dnkl.foot";
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
|
@ -65,8 +65,8 @@ def check_state(name, enablements):
|
|||||||
if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['args'][0]):
|
if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['args'][0]):
|
||||||
raise Exception(f"unexpected args {instance['config']['args']}")
|
raise Exception(f"unexpected args {instance['config']['args']}")
|
||||||
|
|
||||||
if config['confinement']['enablements'] != enablements:
|
if config['enablements'] != enablements:
|
||||||
raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")
|
raise Exception(f"unexpected enablements {instance['config']['enablements']}")
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
@ -17,6 +17,7 @@ func withNixDaemon(
|
|||||||
) {
|
) {
|
||||||
mustRunAppDropShell(ctx, updateConfig(&fst.Config{
|
mustRunAppDropShell(ctx, updateConfig(&fst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: shellPath,
|
Path: shellPath,
|
||||||
Args: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
Args: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
||||||
// start nix-daemon
|
// start nix-daemon
|
||||||
@ -29,16 +30,23 @@ func withNixDaemon(
|
|||||||
// terminate nix-daemon
|
// terminate nix-daemon
|
||||||
" && pkill nix-daemon",
|
" && pkill nix-daemon",
|
||||||
},
|
},
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: app.AppID,
|
|
||||||
Username: "fortify",
|
Username: "fortify",
|
||||||
Inner: path.Join("/data/data", app.ID),
|
Shell: shellPath,
|
||||||
Outer: pathSet.homeDir,
|
Data: pathSet.homeDir,
|
||||||
Sandbox: &fst.SandboxConfig{
|
Dir: path.Join("/data/data", app.ID),
|
||||||
|
ExtraPerms: []*fst.ExtraPermConfig{
|
||||||
|
{Path: dataHome, Execute: true},
|
||||||
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
Identity: app.Identity,
|
||||||
|
|
||||||
|
Container: &fst.ContainerConfig{
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
Userns: true, // nix sandbox requires userns
|
Userns: true, // nix sandbox requires userns
|
||||||
Net: net,
|
Net: net,
|
||||||
Seccomp: seccomp.FlagMultiarch,
|
Seccomp: seccomp.FilterMultiarch,
|
||||||
Tty: dropShell,
|
Tty: dropShell,
|
||||||
Filesystem: []*fst.FilesystemConfig{
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
{Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true},
|
{Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true},
|
||||||
@ -51,11 +59,6 @@ func withNixDaemon(
|
|||||||
Etc: path.Join(pathSet.cacheDir, "etc"),
|
Etc: path.Join(pathSet.cacheDir, "etc"),
|
||||||
AutoEtc: true,
|
AutoEtc: true,
|
||||||
},
|
},
|
||||||
ExtraPerms: []*fst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}), dropShell, beforeFail)
|
}), dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,16 +68,25 @@ func withCacheDir(
|
|||||||
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||||
mustRunAppDropShell(ctx, &fst.Config{
|
mustRunAppDropShell(ctx, &fst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
|
|
||||||
Path: shellPath,
|
Path: shellPath,
|
||||||
Args: []string{shellPath, "-lc", strings.Join(command, " && ")},
|
Args: []string{shellPath, "-lc", strings.Join(command, " && ")},
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: app.AppID,
|
|
||||||
Username: "nixos",
|
Username: "nixos",
|
||||||
Inner: path.Join("/data/data", app.ID, "cache"),
|
Shell: shellPath,
|
||||||
Outer: pathSet.cacheDir, // this also ensures cacheDir via shim
|
Data: pathSet.cacheDir, // this also ensures cacheDir via shim
|
||||||
Sandbox: &fst.SandboxConfig{
|
Dir: path.Join("/data/data", app.ID, "cache"),
|
||||||
|
ExtraPerms: []*fst.ExtraPermConfig{
|
||||||
|
{Path: dataHome, Execute: true},
|
||||||
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
|
{Path: workDir, Execute: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
Identity: app.Identity,
|
||||||
|
|
||||||
|
Container: &fst.ContainerConfig{
|
||||||
Hostname: formatHostname(app.Name) + "-" + action,
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
Seccomp: seccomp.FlagMultiarch,
|
Seccomp: seccomp.FilterMultiarch,
|
||||||
Tty: dropShell,
|
Tty: dropShell,
|
||||||
Filesystem: []*fst.FilesystemConfig{
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
{Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true},
|
{Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true},
|
||||||
@ -88,12 +100,6 @@ func withCacheDir(
|
|||||||
Etc: path.Join(workDir, "etc"),
|
Etc: path.Join(workDir, "etc"),
|
||||||
AutoEtc: true,
|
AutoEtc: true,
|
||||||
},
|
},
|
||||||
ExtraPerms: []*fst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
{Path: workDir, Execute: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, dropShell, beforeFail)
|
}, dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, useSandbox bool) er
|
|||||||
c, toolPath,
|
c, toolPath,
|
||||||
p.seal, true,
|
p.seal, true,
|
||||||
argF, func(container *sandbox.Container) {
|
argF, func(container *sandbox.Container) {
|
||||||
container.Seccomp |= seccomp.FlagMultiarch
|
container.Seccomp |= seccomp.FilterMultiarch
|
||||||
container.Hostname = "fortify-dbus"
|
container.Hostname = "fortify-dbus"
|
||||||
container.CommandContext = p.CommandContext
|
container.CommandContext = p.CommandContext
|
||||||
if output != nil {
|
if output != nil {
|
||||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742234739,
|
"lastModified": 1742655702,
|
||||||
"narHash": "sha256-zFL6zsf/5OztR1NSNQF33dvS1fL/BzVUjabZq4qrtY4=",
|
"narHash": "sha256-jbqlw4sPArFtNtA1s3kLg7/A4fzP4GLk9bGbtUJg0JQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "f6af7280a3390e65c2ad8fd059cdc303426cbd59",
|
"rev": "0948aeedc296f964140d9429223c7e4a0702a1ff",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742512142,
|
"lastModified": 1743231893,
|
||||||
"narHash": "sha256-8XfURTDxOm6+33swQJu/hx6xw1Tznl8vJJN5HwVqckg=",
|
"narHash": "sha256-tpJsHMUPEhEnzySoQxx7+kA+KUtgWqvlcUBqROYNNt0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "7105ae3957700a9646cc4b766f5815b23ed0c682",
|
"rev": "c570c1f5304493cafe133b8d843c7c1c4a10d3a6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
47
fst/app.go
47
fst/app.go
@ -1,47 +0,0 @@
|
|||||||
// Package fst exports shared fortify types.
|
|
||||||
package fst
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type App interface {
|
|
||||||
// ID returns a copy of [fst.ID] held by App.
|
|
||||||
ID() ID
|
|
||||||
|
|
||||||
// Seal determines the outcome of config as a [SealedApp].
|
|
||||||
// The value of config might be overwritten and must not be used again.
|
|
||||||
Seal(config *Config) (SealedApp, error)
|
|
||||||
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SealedApp interface {
|
|
||||||
// Run commits sealed system setup and starts the app process.
|
|
||||||
Run(rs *RunState) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunState stores the outcome of a call to [SealedApp.Run].
|
|
||||||
type RunState struct {
|
|
||||||
// Time is the exact point in time where the process was created.
|
|
||||||
// Location must be set to UTC.
|
|
||||||
//
|
|
||||||
// Time is nil if no process was ever created.
|
|
||||||
Time *time.Time
|
|
||||||
// ExitCode is the value returned by shim.
|
|
||||||
ExitCode int
|
|
||||||
// RevertErr is stored by the deferred revert call.
|
|
||||||
RevertErr error
|
|
||||||
// WaitErr is error returned by the underlying wait syscall.
|
|
||||||
WaitErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paths contains environment-dependent paths used by fortify.
|
|
||||||
type Paths struct {
|
|
||||||
// path to shared directory (usually `/tmp/fortify.%d`)
|
|
||||||
SharePath string `json:"share_path"`
|
|
||||||
// XDG_RUNTIME_DIR value (usually `/run/user/%d`)
|
|
||||||
RuntimePath string `json:"runtime_path"`
|
|
||||||
// application runtime directory (usually `/run/user/%d/fortify`)
|
|
||||||
RunDirPath string `json:"run_dir_path"`
|
|
||||||
}
|
|
132
fst/config.go
132
fst/config.go
@ -1,14 +1,14 @@
|
|||||||
|
// Package fst exports shared fortify types.
|
||||||
package fst
|
package fst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Tmp = "/.fortify"
|
const Tmp = "/.fortify"
|
||||||
|
|
||||||
// Config is used to seal an app
|
// Config is used to seal an app implementation.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// reverse-DNS style arbitrary identifier string from config;
|
// reverse-DNS style arbitrary identifier string from config;
|
||||||
// passed to wayland security-context-v1 as application ID
|
// passed to wayland security-context-v1 as application ID
|
||||||
@ -20,37 +20,40 @@ type Config struct {
|
|||||||
// final args passed to container init
|
// final args passed to container init
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
|
|
||||||
Confinement ConfinementConfig `json:"confinement"`
|
// system services to make available in the container
|
||||||
}
|
Enablements system.Enablement `json:"enablements"`
|
||||||
|
|
||||||
|
// session D-Bus proxy configuration;
|
||||||
|
// nil makes session bus proxy assume built-in defaults
|
||||||
|
SessionBus *dbus.Config `json:"session_bus,omitempty"`
|
||||||
|
// system D-Bus proxy configuration;
|
||||||
|
// nil disables system bus proxy
|
||||||
|
SystemBus *dbus.Config `json:"system_bus,omitempty"`
|
||||||
|
// direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
|
||||||
|
// and the bare socket is mounted to the sandbox
|
||||||
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
|
|
||||||
// ConfinementConfig defines fortified child's confinement
|
|
||||||
type ConfinementConfig struct {
|
|
||||||
// numerical application id, determines uid in the init namespace
|
|
||||||
AppID int `json:"app_id"`
|
|
||||||
// list of supplementary groups to inherit
|
|
||||||
Groups []string `json:"groups"`
|
|
||||||
// passwd username in container, defaults to passwd name of target uid or chronos
|
// passwd username in container, defaults to passwd name of target uid or chronos
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
// home directory in container, empty for outer
|
// absolute path to shell, empty for host shell
|
||||||
Inner string `json:"home_inner"`
|
Shell string `json:"shell,omitempty"`
|
||||||
// home directory in init namespace
|
// absolute path to home directory in the init mount namespace
|
||||||
Outer string `json:"home"`
|
Data string `json:"data"`
|
||||||
// abstract sandbox configuration
|
// directory to enter and use as home in the container mount namespace, empty for Data
|
||||||
Sandbox *SandboxConfig `json:"sandbox"`
|
Dir string `json:"dir"`
|
||||||
// extra acl ops, runs after everything else
|
// extra acl ops, dispatches before container init
|
||||||
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"`
|
||||||
|
|
||||||
// reference to a system D-Bus proxy configuration,
|
// numerical application id, used for init user namespace credentials
|
||||||
// nil value disables system bus proxy
|
Identity int `json:"identity"`
|
||||||
SystemBus *dbus.Config `json:"system_bus,omitempty"`
|
// list of supplementary groups inherited by container processes
|
||||||
// reference to a session D-Bus proxy configuration,
|
Groups []string `json:"groups"`
|
||||||
// nil value makes session bus proxy assume built-in defaults
|
|
||||||
SessionBus *dbus.Config `json:"session_bus,omitempty"`
|
|
||||||
|
|
||||||
// system resources to expose to the container
|
// abstract container configuration baseline
|
||||||
Enablements system.Enablement `json:"enablements"`
|
Container *ContainerConfig `json:"container"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraPermConfig describes an acl update op.
|
||||||
type ExtraPermConfig struct {
|
type ExtraPermConfig struct {
|
||||||
Ensure bool `json:"ensure,omitempty"`
|
Ensure bool `json:"ensure,omitempty"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@ -78,82 +81,3 @@ func (e *ExtraPermConfig) String() string {
|
|||||||
}
|
}
|
||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template returns a fully populated instance of Config.
|
|
||||||
func Template() *Config {
|
|
||||||
return &Config{
|
|
||||||
ID: "org.chromium.Chromium",
|
|
||||||
Path: "/run/current-system/sw/bin/chromium",
|
|
||||||
Args: []string{
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland",
|
|
||||||
},
|
|
||||||
Confinement: ConfinementConfig{
|
|
||||||
AppID: 9,
|
|
||||||
Groups: []string{"video"},
|
|
||||||
Username: "chronos",
|
|
||||||
Outer: "/var/lib/persist/home/org.chromium.Chromium",
|
|
||||||
Inner: "/var/lib/fortify",
|
|
||||||
Sandbox: &SandboxConfig{
|
|
||||||
Hostname: "localhost",
|
|
||||||
Devel: true,
|
|
||||||
Userns: true,
|
|
||||||
Net: true,
|
|
||||||
Dev: true,
|
|
||||||
Seccomp: seccomp.FlagMultiarch,
|
|
||||||
Tty: true,
|
|
||||||
Multiarch: true,
|
|
||||||
MapRealUID: true,
|
|
||||||
DirectWayland: false,
|
|
||||||
// example API credentials pulled from Google Chrome
|
|
||||||
// DO NOT USE THESE IN A REAL BROWSER
|
|
||||||
Env: map[string]string{
|
|
||||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
|
||||||
},
|
|
||||||
Filesystem: []*FilesystemConfig{
|
|
||||||
{Src: "/nix/store"},
|
|
||||||
{Src: "/run/current-system"},
|
|
||||||
{Src: "/run/opengl-driver"},
|
|
||||||
{Src: "/var/db/nix-channels"},
|
|
||||||
{Src: "/var/lib/fortify/u0/org.chromium.Chromium",
|
|
||||||
Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
|
|
||||||
{Src: "/dev/dri", Device: true},
|
|
||||||
},
|
|
||||||
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
|
|
||||||
Etc: "/etc",
|
|
||||||
AutoEtc: true,
|
|
||||||
Cover: []string{"/var/run/nscd"},
|
|
||||||
},
|
|
||||||
ExtraPerms: []*ExtraPermConfig{
|
|
||||||
{Path: "/var/lib/fortify/u0", Ensure: true, Execute: true},
|
|
||||||
{Path: "/var/lib/fortify/u0/org.chromium.Chromium", Read: true, Write: true, Execute: true},
|
|
||||||
},
|
|
||||||
SystemBus: &dbus.Config{
|
|
||||||
See: nil,
|
|
||||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
|
||||||
Own: nil,
|
|
||||||
Call: nil,
|
|
||||||
Broadcast: nil,
|
|
||||||
Log: false,
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
SessionBus: &dbus.Config{
|
|
||||||
See: nil,
|
|
||||||
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
|
||||||
Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"},
|
|
||||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
|
||||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
|
||||||
Log: false,
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
59
fst/container.go
Normal file
59
fst/container.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
|
||||||
|
ContainerConfig struct {
|
||||||
|
// container hostname
|
||||||
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
|
||||||
|
// extra seccomp flags
|
||||||
|
Seccomp seccomp.FilterOpts `json:"seccomp"`
|
||||||
|
// allow ptrace and friends
|
||||||
|
Devel bool `json:"devel,omitempty"`
|
||||||
|
// allow userns creation in container
|
||||||
|
Userns bool `json:"userns,omitempty"`
|
||||||
|
// share host net namespace
|
||||||
|
Net bool `json:"net,omitempty"`
|
||||||
|
// allow dangerous terminal I/O
|
||||||
|
Tty bool `json:"tty,omitempty"`
|
||||||
|
// allow multiarch
|
||||||
|
Multiarch bool `json:"multiarch,omitempty"`
|
||||||
|
|
||||||
|
// initial process environment variables
|
||||||
|
Env map[string]string `json:"env"`
|
||||||
|
// map target user uid to privileged user uid in the user namespace
|
||||||
|
MapRealUID bool `json:"map_real_uid"`
|
||||||
|
|
||||||
|
// pass through all devices
|
||||||
|
Device bool `json:"device,omitempty"`
|
||||||
|
// container host filesystem bind mounts
|
||||||
|
Filesystem []*FilesystemConfig `json:"filesystem"`
|
||||||
|
// create symlinks inside container filesystem
|
||||||
|
Link [][2]string `json:"symlink"`
|
||||||
|
|
||||||
|
// read-only /etc directory
|
||||||
|
Etc string `json:"etc,omitempty"`
|
||||||
|
// automatically set up /etc symlinks
|
||||||
|
AutoEtc bool `json:"auto_etc"`
|
||||||
|
// cover these paths or create them if they do not already exist
|
||||||
|
Cover []string `json:"cover"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilesystemConfig is an abstract representation of a bind mount.
|
||||||
|
FilesystemConfig struct {
|
||||||
|
// mount point in container, same as src if empty
|
||||||
|
Dst string `json:"dst,omitempty"`
|
||||||
|
// host filesystem path to make available to the container
|
||||||
|
Src string `json:"src"`
|
||||||
|
// do not mount filesystem read-only
|
||||||
|
Write bool `json:"write,omitempty"`
|
||||||
|
// do not disable device files
|
||||||
|
Device bool `json:"dev,omitempty"`
|
||||||
|
// fail if the bind mount cannot be established for any reason
|
||||||
|
Must bool `json:"require,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
286
fst/sandbox.go
286
fst/sandbox.go
@ -1,286 +0,0 @@
|
|||||||
package fst
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"maps"
|
|
||||||
"path"
|
|
||||||
"slices"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
|
||||||
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SandboxConfig describes resources made available to the sandbox.
|
|
||||||
type (
|
|
||||||
SandboxConfig struct {
|
|
||||||
// container hostname
|
|
||||||
Hostname string `json:"hostname,omitempty"`
|
|
||||||
|
|
||||||
// extra seccomp flags
|
|
||||||
Seccomp seccomp.SyscallOpts `json:"seccomp"`
|
|
||||||
// allow ptrace and friends
|
|
||||||
Devel bool `json:"devel,omitempty"`
|
|
||||||
// allow userns creation in container
|
|
||||||
Userns bool `json:"userns,omitempty"`
|
|
||||||
// share host net namespace
|
|
||||||
Net bool `json:"net,omitempty"`
|
|
||||||
// expose main process tty
|
|
||||||
Tty bool `json:"tty,omitempty"`
|
|
||||||
// allow multiarch
|
|
||||||
Multiarch bool `json:"multiarch,omitempty"`
|
|
||||||
|
|
||||||
// initial process environment variables
|
|
||||||
Env map[string]string `json:"env"`
|
|
||||||
// map target user uid to privileged user uid in the user namespace
|
|
||||||
MapRealUID bool `json:"map_real_uid"`
|
|
||||||
|
|
||||||
// expose all devices
|
|
||||||
Dev bool `json:"dev,omitempty"`
|
|
||||||
// container host filesystem bind mounts
|
|
||||||
Filesystem []*FilesystemConfig `json:"filesystem"`
|
|
||||||
// create symlinks inside container filesystem
|
|
||||||
Link [][2]string `json:"symlink"`
|
|
||||||
|
|
||||||
// direct access to wayland socket; when this gets set no attempt is made to attach security-context-v1
|
|
||||||
// and the bare socket is mounted to the sandbox
|
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
|
||||||
|
|
||||||
// read-only /etc directory
|
|
||||||
Etc string `json:"etc,omitempty"`
|
|
||||||
// automatically set up /etc symlinks
|
|
||||||
AutoEtc bool `json:"auto_etc"`
|
|
||||||
// cover these paths or create them if they do not already exist
|
|
||||||
Cover []string `json:"cover"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SandboxSys encapsulates system functions used during [sandbox.Container] initialisation.
|
|
||||||
SandboxSys interface {
|
|
||||||
Getuid() int
|
|
||||||
Getgid() int
|
|
||||||
Paths() Paths
|
|
||||||
ReadDir(name string) ([]fs.DirEntry, error)
|
|
||||||
EvalSymlinks(path string) (string, error)
|
|
||||||
|
|
||||||
Println(v ...any)
|
|
||||||
Printf(format string, v ...any)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilesystemConfig is a representation of [sandbox.BindMount].
|
|
||||||
FilesystemConfig struct {
|
|
||||||
// mount point in container, same as src if empty
|
|
||||||
Dst string `json:"dst,omitempty"`
|
|
||||||
// host filesystem path to make available to the container
|
|
||||||
Src string `json:"src"`
|
|
||||||
// do not mount filesystem read-only
|
|
||||||
Write bool `json:"write,omitempty"`
|
|
||||||
// do not disable device files
|
|
||||||
Device bool `json:"dev,omitempty"`
|
|
||||||
// fail if the bind mount cannot be established for any reason
|
|
||||||
Must bool `json:"require,omitempty"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToContainer initialises [sandbox.Params] via [SandboxConfig].
|
|
||||||
// Note that remaining container setup must be queued by the [App] implementation.
|
|
||||||
func (s *SandboxConfig) ToContainer(sys SandboxSys, uid, gid *int) (*sandbox.Params, map[string]string, error) {
|
|
||||||
if s == nil {
|
|
||||||
return nil, nil, syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
container := &sandbox.Params{
|
|
||||||
Hostname: s.Hostname,
|
|
||||||
Ops: new(sandbox.Ops),
|
|
||||||
Seccomp: s.Seccomp,
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Multiarch {
|
|
||||||
container.Seccomp |= seccomp.FlagMultiarch
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this is only 4 KiB of memory on a 64-bit system,
|
|
||||||
permissive defaults on NixOS results in around 100 entries
|
|
||||||
so this capacity should eliminate copies for most setups */
|
|
||||||
*container.Ops = slices.Grow(*container.Ops, 1<<8)
|
|
||||||
|
|
||||||
if s.Devel {
|
|
||||||
container.Flags |= sandbox.FAllowDevel
|
|
||||||
}
|
|
||||||
if s.Userns {
|
|
||||||
container.Flags |= sandbox.FAllowUserns
|
|
||||||
}
|
|
||||||
if s.Net {
|
|
||||||
container.Flags |= sandbox.FAllowNet
|
|
||||||
}
|
|
||||||
if s.Tty {
|
|
||||||
container.Flags |= sandbox.FAllowTTY
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.MapRealUID {
|
|
||||||
/* some programs fail to connect to dbus session running as a different uid
|
|
||||||
so this workaround is introduced to map priv-side caller uid in container */
|
|
||||||
container.Uid = sys.Getuid()
|
|
||||||
*uid = container.Uid
|
|
||||||
container.Gid = sys.Getgid()
|
|
||||||
*gid = container.Gid
|
|
||||||
} else {
|
|
||||||
*uid = sandbox.OverflowUid()
|
|
||||||
*gid = sandbox.OverflowGid()
|
|
||||||
}
|
|
||||||
|
|
||||||
container.
|
|
||||||
Proc("/proc").
|
|
||||||
Tmpfs(Tmp, 1<<12, 0755)
|
|
||||||
|
|
||||||
if !s.Dev {
|
|
||||||
container.Dev("/dev").Mqueue("/dev/mqueue")
|
|
||||||
} else {
|
|
||||||
container.Bind("/dev", "/dev", sandbox.BindDevice)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* retrieve paths and hide them if they're made available in the sandbox;
|
|
||||||
this feature tries to improve user experience of permissive defaults, and
|
|
||||||
to warn about issues in custom configuration; it is NOT a security feature
|
|
||||||
and should not be treated as such, ALWAYS be careful with what you bind */
|
|
||||||
var hidePaths []string
|
|
||||||
sc := sys.Paths()
|
|
||||||
hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
|
|
||||||
_, systemBusAddr := dbus.Address()
|
|
||||||
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else {
|
|
||||||
// there is usually only one, do not preallocate
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.Method != "unix" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, pair := range entry.Values {
|
|
||||||
if pair[0] == "path" {
|
|
||||||
if path.IsAbs(pair[1]) {
|
|
||||||
// get parent dir of socket
|
|
||||||
dir := path.Dir(pair[1])
|
|
||||||
if dir == "." || dir == "/" {
|
|
||||||
sys.Printf("dbus socket %q is in an unusual location", pair[1])
|
|
||||||
}
|
|
||||||
hidePaths = append(hidePaths, dir)
|
|
||||||
} else {
|
|
||||||
sys.Printf("dbus socket %q is not absolute", pair[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hidePathMatch := make([]bool, len(hidePaths))
|
|
||||||
for i := range hidePaths {
|
|
||||||
if err := evalSymlinks(sys, &hidePaths[i]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range s.Filesystem {
|
|
||||||
if c == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !path.IsAbs(c.Src) {
|
|
||||||
return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
|
|
||||||
}
|
|
||||||
|
|
||||||
dest := c.Dst
|
|
||||||
if c.Dst == "" {
|
|
||||||
dest = c.Src
|
|
||||||
} else if !path.IsAbs(dest) {
|
|
||||||
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
|
|
||||||
}
|
|
||||||
|
|
||||||
srcH := c.Src
|
|
||||||
if err := evalSymlinks(sys, &srcH); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range hidePaths {
|
|
||||||
// skip matched entries
|
|
||||||
if hidePathMatch[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if ok {
|
|
||||||
hidePathMatch[i] = true
|
|
||||||
sys.Printf("hiding paths from %q", c.Src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags int
|
|
||||||
if c.Write {
|
|
||||||
flags |= sandbox.BindWritable
|
|
||||||
}
|
|
||||||
if c.Device {
|
|
||||||
flags |= sandbox.BindDevice | sandbox.BindWritable
|
|
||||||
}
|
|
||||||
if !c.Must {
|
|
||||||
flags |= sandbox.BindOptional
|
|
||||||
}
|
|
||||||
container.Bind(c.Src, dest, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cover matched paths
|
|
||||||
for i, ok := range hidePathMatch {
|
|
||||||
if ok {
|
|
||||||
container.Tmpfs(hidePaths[i], 1<<13, 0755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range s.Link {
|
|
||||||
container.Link(l[0], l[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// perf: this might work better if implemented as a setup op in container init
|
|
||||||
if !s.AutoEtc {
|
|
||||||
if s.Etc != "" {
|
|
||||||
container.Bind(s.Etc, "/etc", 0)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
etcPath := s.Etc
|
|
||||||
if etcPath == "" {
|
|
||||||
etcPath = "/etc"
|
|
||||||
}
|
|
||||||
container.Bind(etcPath, Tmp+"/etc", 0)
|
|
||||||
|
|
||||||
// link host /etc contents to prevent dropping passwd/group bind mounts
|
|
||||||
if d, err := sys.ReadDir(etcPath); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else {
|
|
||||||
for _, ent := range d {
|
|
||||||
n := ent.Name()
|
|
||||||
switch n {
|
|
||||||
case "passwd":
|
|
||||||
case "group":
|
|
||||||
|
|
||||||
case "mtab":
|
|
||||||
container.Link("/proc/mounts", "/etc/"+n)
|
|
||||||
default:
|
|
||||||
container.Link(Tmp+"/etc/"+n, "/etc/"+n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return container, maps.Clone(s.Env), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalSymlinks(sys SandboxSys, v *string) error {
|
|
||||||
if p, err := sys.EvalSymlinks(*v); err != nil {
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sys.Printf("path %q does not yet exist", *v)
|
|
||||||
} else {
|
|
||||||
*v = p
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
91
fst/template.go
Normal file
91
fst/template.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
||||||
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Template returns a fully populated instance of Config.
|
||||||
|
func Template() *Config {
|
||||||
|
return &Config{
|
||||||
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
|
Path: "/run/current-system/sw/bin/chromium",
|
||||||
|
Args: []string{
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland",
|
||||||
|
},
|
||||||
|
|
||||||
|
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
||||||
|
|
||||||
|
SessionBus: &dbus.Config{
|
||||||
|
See: nil,
|
||||||
|
Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
|
||||||
|
Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"},
|
||||||
|
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||||
|
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||||
|
Log: false,
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
SystemBus: &dbus.Config{
|
||||||
|
See: nil,
|
||||||
|
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||||
|
Own: nil,
|
||||||
|
Call: nil,
|
||||||
|
Broadcast: nil,
|
||||||
|
Log: false,
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
DirectWayland: false,
|
||||||
|
|
||||||
|
Username: "chronos",
|
||||||
|
Shell: "/run/current-system/sw/bin/zsh",
|
||||||
|
Data: "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
Dir: "/data/data/org.chromium.Chromium",
|
||||||
|
ExtraPerms: []*ExtraPermConfig{
|
||||||
|
{Path: "/var/lib/fortify/u0", Ensure: true, Execute: true},
|
||||||
|
{Path: "/var/lib/fortify/u0/org.chromium.Chromium", Read: true, Write: true, Execute: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
Identity: 9,
|
||||||
|
Groups: []string{"video", "dialout", "plugdev"},
|
||||||
|
|
||||||
|
Container: &ContainerConfig{
|
||||||
|
Hostname: "localhost",
|
||||||
|
Devel: true,
|
||||||
|
Userns: true,
|
||||||
|
Net: true,
|
||||||
|
Device: true,
|
||||||
|
Seccomp: seccomp.FilterMultiarch,
|
||||||
|
Tty: true,
|
||||||
|
Multiarch: true,
|
||||||
|
MapRealUID: true,
|
||||||
|
// example API credentials pulled from Google Chrome
|
||||||
|
// DO NOT USE THESE IN A REAL BROWSER
|
||||||
|
Env: map[string]string{
|
||||||
|
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
|
||||||
|
},
|
||||||
|
Filesystem: []*FilesystemConfig{
|
||||||
|
{Src: "/nix/store"},
|
||||||
|
{Src: "/run/current-system"},
|
||||||
|
{Src: "/run/opengl-driver"},
|
||||||
|
{Src: "/var/db/nix-channels"},
|
||||||
|
{Src: "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
Dst: "/data/data/org.chromium.Chromium", Write: true, Must: true},
|
||||||
|
{Src: "/dev/dri", Device: true},
|
||||||
|
},
|
||||||
|
Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
|
||||||
|
Etc: "/etc",
|
||||||
|
AutoEtc: true,
|
||||||
|
Cover: []string{"/var/run/nscd"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
140
fst/template_test.go
Normal file
140
fst/template_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package fst_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplate(t *testing.T) {
|
||||||
|
const want = `{
|
||||||
|
"id": "org.chromium.Chromium",
|
||||||
|
"path": "/run/current-system/sw/bin/chromium",
|
||||||
|
"args": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
|
],
|
||||||
|
"enablements": 13,
|
||||||
|
"session_bus": {
|
||||||
|
"see": null,
|
||||||
|
"talk": [
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager"
|
||||||
|
],
|
||||||
|
"own": [
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"
|
||||||
|
],
|
||||||
|
"call": {
|
||||||
|
"org.freedesktop.portal.*": "*"
|
||||||
|
},
|
||||||
|
"broadcast": {
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
||||||
|
},
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
|
"system_bus": {
|
||||||
|
"see": null,
|
||||||
|
"talk": [
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower"
|
||||||
|
],
|
||||||
|
"own": null,
|
||||||
|
"call": null,
|
||||||
|
"broadcast": null,
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
|
"username": "chronos",
|
||||||
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
|
"data": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"dir": "/data/data/org.chromium.Chromium",
|
||||||
|
"extra_perms": [
|
||||||
|
{
|
||||||
|
"ensure": true,
|
||||||
|
"path": "/var/lib/fortify/u0",
|
||||||
|
"x": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"r": true,
|
||||||
|
"w": true,
|
||||||
|
"x": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identity": 9,
|
||||||
|
"groups": [
|
||||||
|
"video",
|
||||||
|
"dialout",
|
||||||
|
"plugdev"
|
||||||
|
],
|
||||||
|
"container": {
|
||||||
|
"hostname": "localhost",
|
||||||
|
"seccomp": 32,
|
||||||
|
"devel": true,
|
||||||
|
"userns": true,
|
||||||
|
"net": true,
|
||||||
|
"tty": true,
|
||||||
|
"multiarch": true,
|
||||||
|
"env": {
|
||||||
|
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
|
},
|
||||||
|
"map_real_uid": true,
|
||||||
|
"device": true,
|
||||||
|
"filesystem": [
|
||||||
|
{
|
||||||
|
"src": "/nix/store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/run/current-system"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/run/opengl-driver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/var/db/nix-channels"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dst": "/data/data/org.chromium.Chromium",
|
||||||
|
"src": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"write": true,
|
||||||
|
"require": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/dev/dri",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"symlink": [
|
||||||
|
[
|
||||||
|
"/run/user/65534",
|
||||||
|
"/run/user/150"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"etc": "/etc",
|
||||||
|
"auto_etc": true,
|
||||||
|
"cover": [
|
||||||
|
"/var/run/nscd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
if p, err := json.MarshalIndent(fst.Template(), "", "\t"); err != nil {
|
||||||
|
t.Fatalf("cannot marshal: %v", err)
|
||||||
|
} else if s := string(p); s != want {
|
||||||
|
t.Fatalf("Template:\n%s\nwant:\n%s",
|
||||||
|
s, want)
|
||||||
|
}
|
||||||
|
}
|
@ -1,82 +1,59 @@
|
|||||||
|
// Package app defines the generic [App] interface.
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"syscall"
|
||||||
"fmt"
|
"time"
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, os sys.State) (fst.App, error) {
|
type App interface {
|
||||||
a := new(app)
|
// ID returns a copy of [ID] held by App.
|
||||||
a.sys = os
|
ID() ID
|
||||||
a.ctx = ctx
|
|
||||||
|
|
||||||
id := new(fst.ID)
|
// Seal determines the outcome of config as a [SealedApp].
|
||||||
err := fst.NewAppID(id)
|
// The value of config might be overwritten and must not be used again.
|
||||||
a.id = newID(id)
|
Seal(config *fst.Config) (SealedApp, error)
|
||||||
|
|
||||||
return a, err
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustNew(ctx context.Context, os sys.State) fst.App {
|
type SealedApp interface {
|
||||||
a, err := New(ctx, os)
|
// Run commits sealed system setup and starts the app process.
|
||||||
if err != nil {
|
Run(rs *RunState) error
|
||||||
log.Fatalf("cannot create app: %v", err)
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type app struct {
|
// RunState stores the outcome of a call to [SealedApp.Run].
|
||||||
id *stringPair[fst.ID]
|
type RunState struct {
|
||||||
sys sys.State
|
// Time is the exact point in time where the process was created.
|
||||||
ctx context.Context
|
// Location must be set to UTC.
|
||||||
|
//
|
||||||
|
// Time is nil if no process was ever created.
|
||||||
|
Time *time.Time
|
||||||
|
// RevertErr is stored by the deferred revert call.
|
||||||
|
RevertErr error
|
||||||
|
// WaitErr is the generic error value created by the standard library.
|
||||||
|
WaitErr error
|
||||||
|
|
||||||
*outcome
|
syscall.WaitStatus
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) ID() fst.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
// SetStart stores the current time in [RunState] once.
|
||||||
|
func (rs *RunState) SetStart() {
|
||||||
func (a *app) String() string {
|
if rs.Time != nil {
|
||||||
if a == nil {
|
panic("attempted to store time twice")
|
||||||
return "(invalid app)"
|
}
|
||||||
|
now := time.Now().UTC()
|
||||||
|
rs.Time = &now
|
||||||
}
|
}
|
||||||
|
|
||||||
a.mu.RLock()
|
// Paths contains environment-dependent paths used by fortify.
|
||||||
defer a.mu.RUnlock()
|
type Paths struct {
|
||||||
|
// path to shared directory (usually `/tmp/fortify.%d`)
|
||||||
if a.outcome != nil {
|
SharePath string `json:"share_path"`
|
||||||
if a.outcome.user.uid == nil {
|
// XDG_RUNTIME_DIR value (usually `/run/user/%d`)
|
||||||
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
RuntimePath string `json:"runtime_path"`
|
||||||
}
|
// application runtime directory (usually `/run/user/%d/fortify`)
|
||||||
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
|
RunDirPath string `json:"run_dir_path"`
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("(unsealed app %s)", a.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) Seal(config *fst.Config) (fst.SealedApp, error) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
if a.outcome != nil {
|
|
||||||
panic("app sealed twice")
|
|
||||||
}
|
|
||||||
if config == nil {
|
|
||||||
return nil, fmsg.WrapError(ErrConfig,
|
|
||||||
"attempted to seal app with nil config")
|
|
||||||
}
|
|
||||||
|
|
||||||
seal := new(outcome)
|
|
||||||
seal.id = a.id
|
|
||||||
err := seal.finalise(a.ctx, a.sys, config)
|
|
||||||
if err == nil {
|
|
||||||
a.outcome = seal
|
|
||||||
}
|
|
||||||
return seal, err
|
|
||||||
}
|
}
|
||||||
|
@ -1,220 +0,0 @@
|
|||||||
package app_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCasesNixos = []sealTestCase{
|
|
||||||
{
|
|
||||||
"nixos chromium direct wayland", new(stubNixOS),
|
|
||||||
&fst.Config{
|
|
||||||
ID: "org.chromium.Chromium",
|
|
||||||
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
|
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: 1, Groups: []string{}, Username: "u0_a1",
|
|
||||||
Outer: "/var/lib/persist/module/fortify/0/1",
|
|
||||||
Sandbox: &fst.SandboxConfig{
|
|
||||||
Userns: true, Net: true, MapRealUID: true, DirectWayland: true, Env: nil, AutoEtc: true,
|
|
||||||
Filesystem: []*fst.FilesystemConfig{
|
|
||||||
{Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true},
|
|
||||||
{Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true},
|
|
||||||
{Src: "/sys/block"}, {Src: "/sys/bus"}, {Src: "/sys/class"}, {Src: "/sys/dev"}, {Src: "/sys/devices"},
|
|
||||||
{Src: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true},
|
|
||||||
},
|
|
||||||
Cover: []string{"/var/run/nscd"},
|
|
||||||
},
|
|
||||||
SystemBus: &dbus.Config{
|
|
||||||
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
SessionBus: &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5", "org.kde.kwalletd6",
|
|
||||||
},
|
|
||||||
Own: []string{
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*",
|
|
||||||
},
|
|
||||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fst.ID{
|
|
||||||
0x8e, 0x2c, 0x76, 0xb0,
|
|
||||||
0x66, 0xda, 0xbe, 0x57,
|
|
||||||
0x4c, 0xf0, 0x73, 0xbd,
|
|
||||||
0xb4, 0x6e, 0xb5, 0xc1,
|
|
||||||
},
|
|
||||||
system.New(1000001).
|
|
||||||
Ensure("/tmp/fortify.1971", 0711).
|
|
||||||
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
|
|
||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
|
||||||
Ephemeral(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
|
||||||
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
|
||||||
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
|
|
||||||
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
|
||||||
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
|
||||||
MustProxyDBus("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5", "org.kde.kwalletd6",
|
|
||||||
},
|
|
||||||
Own: []string{
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*",
|
|
||||||
},
|
|
||||||
Call: map[string]string{}, Broadcast: map[string]string{},
|
|
||||||
Filter: true,
|
|
||||||
}, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
}).
|
|
||||||
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
|
||||||
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
|
||||||
&sandbox.Params{
|
|
||||||
Uid: 1971,
|
|
||||||
Gid: 100,
|
|
||||||
Flags: sandbox.FAllowNet | sandbox.FAllowUserns,
|
|
||||||
Dir: "/var/lib/persist/module/fortify/0/1",
|
|
||||||
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
|
|
||||||
Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
|
|
||||||
Env: []string{
|
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
|
|
||||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
|
||||||
"HOME=/var/lib/persist/module/fortify/0/1",
|
|
||||||
"PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
|
|
||||||
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
|
||||||
"TERM=xterm-256color",
|
|
||||||
"USER=u0_a1",
|
|
||||||
"WAYLAND_DISPLAY=wayland-0",
|
|
||||||
"XDG_RUNTIME_DIR=/run/user/1971",
|
|
||||||
"XDG_SESSION_CLASS=user",
|
|
||||||
"XDG_SESSION_TYPE=tty",
|
|
||||||
},
|
|
||||||
Ops: new(sandbox.Ops).
|
|
||||||
Proc("/proc").
|
|
||||||
Tmpfs(fst.Tmp, 4096, 0755).
|
|
||||||
Dev("/dev").Mqueue("/dev/mqueue").
|
|
||||||
Bind("/bin", "/bin", 0).
|
|
||||||
Bind("/usr/bin", "/usr/bin", 0).
|
|
||||||
Bind("/nix/store", "/nix/store", 0).
|
|
||||||
Bind("/run/current-system", "/run/current-system", 0).
|
|
||||||
Bind("/sys/block", "/sys/block", sandbox.BindOptional).
|
|
||||||
Bind("/sys/bus", "/sys/bus", sandbox.BindOptional).
|
|
||||||
Bind("/sys/class", "/sys/class", sandbox.BindOptional).
|
|
||||||
Bind("/sys/dev", "/sys/dev", sandbox.BindOptional).
|
|
||||||
Bind("/sys/devices", "/sys/devices", sandbox.BindOptional).
|
|
||||||
Bind("/run/opengl-driver", "/run/opengl-driver", 0).
|
|
||||||
Bind("/dev/dri", "/dev/dri", sandbox.BindDevice|sandbox.BindWritable|sandbox.BindOptional).
|
|
||||||
Bind("/etc", fst.Tmp+"/etc", 0).
|
|
||||||
Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
|
||||||
Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
|
||||||
Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
|
|
||||||
Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
|
|
||||||
Link(fst.Tmp+"/etc/default", "/etc/default").
|
|
||||||
Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
|
|
||||||
Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
|
|
||||||
Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
|
|
||||||
Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
|
|
||||||
Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
|
|
||||||
Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
|
|
||||||
Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
|
|
||||||
Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
|
|
||||||
Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
|
|
||||||
Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
|
|
||||||
Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
|
|
||||||
Link(fst.Tmp+"/etc/issue", "/etc/issue").
|
|
||||||
Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
|
|
||||||
Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
|
|
||||||
Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
|
|
||||||
Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
|
|
||||||
Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
|
|
||||||
Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
|
|
||||||
Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
|
|
||||||
Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
|
|
||||||
Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
|
|
||||||
Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
|
|
||||||
Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
|
|
||||||
Link("/proc/mounts", "/etc/mtab").
|
|
||||||
Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
|
|
||||||
Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
|
|
||||||
Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
|
|
||||||
Link(fst.Tmp+"/etc/nix", "/etc/nix").
|
|
||||||
Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
|
|
||||||
Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
|
|
||||||
Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
|
|
||||||
Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
|
|
||||||
Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
|
|
||||||
Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
|
|
||||||
Link(fst.Tmp+"/etc/pam", "/etc/pam").
|
|
||||||
Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
|
|
||||||
Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
|
|
||||||
Link(fst.Tmp+"/etc/pki", "/etc/pki").
|
|
||||||
Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
|
|
||||||
Link(fst.Tmp+"/etc/profile", "/etc/profile").
|
|
||||||
Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
|
|
||||||
Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
|
|
||||||
Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
|
|
||||||
Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
|
|
||||||
Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
|
|
||||||
Link(fst.Tmp+"/etc/samba", "/etc/samba").
|
|
||||||
Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
|
|
||||||
Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
|
|
||||||
Link(fst.Tmp+"/etc/services", "/etc/services").
|
|
||||||
Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
|
|
||||||
Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
|
|
||||||
Link(fst.Tmp+"/etc/shells", "/etc/shells").
|
|
||||||
Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
|
|
||||||
Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
|
|
||||||
Link(fst.Tmp+"/etc/static", "/etc/static").
|
|
||||||
Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
|
|
||||||
Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
|
|
||||||
Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
|
|
||||||
Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
|
|
||||||
Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
|
|
||||||
Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
|
|
||||||
Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
|
|
||||||
Link(fst.Tmp+"/etc/udev", "/etc/udev").
|
|
||||||
Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
|
|
||||||
Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
|
|
||||||
Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
|
|
||||||
Link(fst.Tmp+"/etc/X11", "/etc/X11").
|
|
||||||
Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
|
|
||||||
Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
|
|
||||||
Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
|
|
||||||
Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
|
|
||||||
Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
|
|
||||||
Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
|
|
||||||
Tmpfs("/run/user", 4096, 0755).
|
|
||||||
Tmpfs("/run/user/1971", 8388608, 0700).
|
|
||||||
Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", sandbox.BindWritable).
|
|
||||||
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", sandbox.BindWritable).
|
|
||||||
Place("/etc/passwd", []byte("u0_a1:x:1971:100:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place("/etc/group", []byte("fortify:x:100:\n")).
|
|
||||||
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0", 0).
|
|
||||||
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native", 0).
|
|
||||||
Place(fst.Tmp+"/pulse-cookie", nil).
|
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus", 0).
|
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
|
||||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,384 +0,0 @@
|
|||||||
package app_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCasesPd = []sealTestCase{
|
|
||||||
{
|
|
||||||
"nixos permissive defaults no enablements", new(stubNixOS),
|
|
||||||
&fst.Config{
|
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: 0,
|
|
||||||
Username: "chronos",
|
|
||||||
Outer: "/home/chronos",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fst.ID{
|
|
||||||
0x4a, 0x45, 0x0b, 0x65,
|
|
||||||
0x96, 0xd7, 0xbc, 0x15,
|
|
||||||
0xbd, 0x01, 0x78, 0x0e,
|
|
||||||
0xb9, 0xa6, 0x07, 0xac,
|
|
||||||
},
|
|
||||||
system.New(1000000).
|
|
||||||
Ensure("/tmp/fortify.1971", 0711).
|
|
||||||
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
|
|
||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
|
||||||
Ephemeral(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac", 0711).
|
|
||||||
Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
|
||||||
&sandbox.Params{
|
|
||||||
Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
|
|
||||||
Dir: "/home/chronos",
|
|
||||||
Path: "/run/current-system/sw/bin/zsh",
|
|
||||||
Args: []string{"/run/current-system/sw/bin/zsh"},
|
|
||||||
Env: []string{
|
|
||||||
"HOME=/home/chronos",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
|
||||||
"TERM=xterm-256color",
|
|
||||||
"USER=chronos",
|
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534",
|
|
||||||
"XDG_SESSION_CLASS=user",
|
|
||||||
"XDG_SESSION_TYPE=tty",
|
|
||||||
},
|
|
||||||
Ops: new(sandbox.Ops).
|
|
||||||
Proc("/proc").
|
|
||||||
Tmpfs(fst.Tmp, 4096, 0755).
|
|
||||||
Dev("/dev").Mqueue("/dev/mqueue").
|
|
||||||
Bind("/bin", "/bin", sandbox.BindWritable).
|
|
||||||
Bind("/boot", "/boot", sandbox.BindWritable).
|
|
||||||
Bind("/home", "/home", sandbox.BindWritable).
|
|
||||||
Bind("/lib", "/lib", sandbox.BindWritable).
|
|
||||||
Bind("/lib64", "/lib64", sandbox.BindWritable).
|
|
||||||
Bind("/nix", "/nix", sandbox.BindWritable).
|
|
||||||
Bind("/root", "/root", sandbox.BindWritable).
|
|
||||||
Bind("/run", "/run", sandbox.BindWritable).
|
|
||||||
Bind("/srv", "/srv", sandbox.BindWritable).
|
|
||||||
Bind("/sys", "/sys", sandbox.BindWritable).
|
|
||||||
Bind("/usr", "/usr", sandbox.BindWritable).
|
|
||||||
Bind("/var", "/var", sandbox.BindWritable).
|
|
||||||
Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
|
||||||
Tmpfs("/run/user/1971", 8192, 0755).
|
|
||||||
Tmpfs("/run/dbus", 8192, 0755).
|
|
||||||
Bind("/etc", fst.Tmp+"/etc", 0).
|
|
||||||
Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
|
||||||
Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
|
||||||
Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
|
|
||||||
Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
|
|
||||||
Link(fst.Tmp+"/etc/default", "/etc/default").
|
|
||||||
Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
|
|
||||||
Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
|
|
||||||
Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
|
|
||||||
Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
|
|
||||||
Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
|
|
||||||
Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
|
|
||||||
Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
|
|
||||||
Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
|
|
||||||
Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
|
|
||||||
Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
|
|
||||||
Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
|
|
||||||
Link(fst.Tmp+"/etc/issue", "/etc/issue").
|
|
||||||
Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
|
|
||||||
Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
|
|
||||||
Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
|
|
||||||
Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
|
|
||||||
Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
|
|
||||||
Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
|
|
||||||
Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
|
|
||||||
Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
|
|
||||||
Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
|
|
||||||
Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
|
|
||||||
Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
|
|
||||||
Link("/proc/mounts", "/etc/mtab").
|
|
||||||
Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
|
|
||||||
Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
|
|
||||||
Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
|
|
||||||
Link(fst.Tmp+"/etc/nix", "/etc/nix").
|
|
||||||
Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
|
|
||||||
Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
|
|
||||||
Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
|
|
||||||
Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
|
|
||||||
Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
|
|
||||||
Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
|
|
||||||
Link(fst.Tmp+"/etc/pam", "/etc/pam").
|
|
||||||
Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
|
|
||||||
Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
|
|
||||||
Link(fst.Tmp+"/etc/pki", "/etc/pki").
|
|
||||||
Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
|
|
||||||
Link(fst.Tmp+"/etc/profile", "/etc/profile").
|
|
||||||
Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
|
|
||||||
Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
|
|
||||||
Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
|
|
||||||
Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
|
|
||||||
Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
|
|
||||||
Link(fst.Tmp+"/etc/samba", "/etc/samba").
|
|
||||||
Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
|
|
||||||
Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
|
|
||||||
Link(fst.Tmp+"/etc/services", "/etc/services").
|
|
||||||
Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
|
|
||||||
Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
|
|
||||||
Link(fst.Tmp+"/etc/shells", "/etc/shells").
|
|
||||||
Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
|
|
||||||
Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
|
|
||||||
Link(fst.Tmp+"/etc/static", "/etc/static").
|
|
||||||
Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
|
|
||||||
Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
|
|
||||||
Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
|
|
||||||
Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
|
|
||||||
Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
|
|
||||||
Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
|
|
||||||
Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
|
|
||||||
Link(fst.Tmp+"/etc/udev", "/etc/udev").
|
|
||||||
Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
|
|
||||||
Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
|
|
||||||
Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
|
|
||||||
Link(fst.Tmp+"/etc/X11", "/etc/X11").
|
|
||||||
Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
|
|
||||||
Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
|
|
||||||
Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
|
|
||||||
Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
|
|
||||||
Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
|
|
||||||
Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
|
|
||||||
Tmpfs("/run/user", 4096, 0755).
|
|
||||||
Tmpfs("/run/user/65534", 8388608, 0700).
|
|
||||||
Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", sandbox.BindWritable).
|
|
||||||
Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
|
|
||||||
Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place("/etc/group", []byte("fortify:x:65534:\n")).
|
|
||||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nixos permissive defaults chromium", new(stubNixOS),
|
|
||||||
&fst.Config{
|
|
||||||
ID: "org.chromium.Chromium",
|
|
||||||
Args: []string{"zsh", "-c", "exec chromium "},
|
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: 9,
|
|
||||||
Groups: []string{"video"},
|
|
||||||
Username: "chronos",
|
|
||||||
Outer: "/home/chronos",
|
|
||||||
SessionBus: &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager",
|
|
||||||
},
|
|
||||||
Own: []string{
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*",
|
|
||||||
},
|
|
||||||
Call: map[string]string{
|
|
||||||
"org.freedesktop.portal.*": "*",
|
|
||||||
},
|
|
||||||
Broadcast: map[string]string{
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
SystemBus: &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
},
|
|
||||||
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fst.ID{
|
|
||||||
0xeb, 0xf0, 0x83, 0xd1,
|
|
||||||
0xb1, 0x75, 0x91, 0x17,
|
|
||||||
0x82, 0xd4, 0x13, 0x36,
|
|
||||||
0x9b, 0x64, 0xce, 0x7c,
|
|
||||||
},
|
|
||||||
system.New(1000009).
|
|
||||||
Ensure("/tmp/fortify.1971", 0711).
|
|
||||||
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
|
|
||||||
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
|
||||||
Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0711).
|
|
||||||
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
|
|
||||||
Ensure("/tmp/fortify.1971/wayland", 0711).
|
|
||||||
Wayland(new(*os.File), "/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
|
||||||
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
|
|
||||||
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
|
||||||
MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager",
|
|
||||||
},
|
|
||||||
Own: []string{
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*",
|
|
||||||
},
|
|
||||||
Call: map[string]string{
|
|
||||||
"org.freedesktop.portal.*": "*",
|
|
||||||
},
|
|
||||||
Broadcast: map[string]string{
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
}, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
|
||||||
Talk: []string{
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower",
|
|
||||||
},
|
|
||||||
Filter: true,
|
|
||||||
}).
|
|
||||||
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
|
||||||
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
|
||||||
&sandbox.Params{
|
|
||||||
Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
|
|
||||||
Dir: "/home/chronos",
|
|
||||||
Path: "/run/current-system/sw/bin/zsh",
|
|
||||||
Args: []string{"zsh", "-c", "exec chromium "},
|
|
||||||
Env: []string{
|
|
||||||
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
|
|
||||||
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
|
||||||
"HOME=/home/chronos",
|
|
||||||
"PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
|
|
||||||
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
|
|
||||||
"SHELL=/run/current-system/sw/bin/zsh",
|
|
||||||
"TERM=xterm-256color",
|
|
||||||
"USER=chronos",
|
|
||||||
"WAYLAND_DISPLAY=wayland-0",
|
|
||||||
"XDG_RUNTIME_DIR=/run/user/65534",
|
|
||||||
"XDG_SESSION_CLASS=user",
|
|
||||||
"XDG_SESSION_TYPE=tty",
|
|
||||||
},
|
|
||||||
Ops: new(sandbox.Ops).
|
|
||||||
Proc("/proc").
|
|
||||||
Tmpfs(fst.Tmp, 4096, 0755).
|
|
||||||
Dev("/dev").Mqueue("/dev/mqueue").
|
|
||||||
Bind("/bin", "/bin", sandbox.BindWritable).
|
|
||||||
Bind("/boot", "/boot", sandbox.BindWritable).
|
|
||||||
Bind("/home", "/home", sandbox.BindWritable).
|
|
||||||
Bind("/lib", "/lib", sandbox.BindWritable).
|
|
||||||
Bind("/lib64", "/lib64", sandbox.BindWritable).
|
|
||||||
Bind("/nix", "/nix", sandbox.BindWritable).
|
|
||||||
Bind("/root", "/root", sandbox.BindWritable).
|
|
||||||
Bind("/run", "/run", sandbox.BindWritable).
|
|
||||||
Bind("/srv", "/srv", sandbox.BindWritable).
|
|
||||||
Bind("/sys", "/sys", sandbox.BindWritable).
|
|
||||||
Bind("/usr", "/usr", sandbox.BindWritable).
|
|
||||||
Bind("/var", "/var", sandbox.BindWritable).
|
|
||||||
Bind("/dev/dri", "/dev/dri", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
|
||||||
Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
|
||||||
Tmpfs("/run/user/1971", 8192, 0755).
|
|
||||||
Tmpfs("/run/dbus", 8192, 0755).
|
|
||||||
Bind("/etc", fst.Tmp+"/etc", 0).
|
|
||||||
Link(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
|
||||||
Link(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
|
||||||
Link(fst.Tmp+"/etc/binfmt.d", "/etc/binfmt.d").
|
|
||||||
Link(fst.Tmp+"/etc/dbus-1", "/etc/dbus-1").
|
|
||||||
Link(fst.Tmp+"/etc/default", "/etc/default").
|
|
||||||
Link(fst.Tmp+"/etc/ethertypes", "/etc/ethertypes").
|
|
||||||
Link(fst.Tmp+"/etc/fonts", "/etc/fonts").
|
|
||||||
Link(fst.Tmp+"/etc/fstab", "/etc/fstab").
|
|
||||||
Link(fst.Tmp+"/etc/fuse.conf", "/etc/fuse.conf").
|
|
||||||
Link(fst.Tmp+"/etc/host.conf", "/etc/host.conf").
|
|
||||||
Link(fst.Tmp+"/etc/hostid", "/etc/hostid").
|
|
||||||
Link(fst.Tmp+"/etc/hostname", "/etc/hostname").
|
|
||||||
Link(fst.Tmp+"/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
|
|
||||||
Link(fst.Tmp+"/etc/hosts", "/etc/hosts").
|
|
||||||
Link(fst.Tmp+"/etc/inputrc", "/etc/inputrc").
|
|
||||||
Link(fst.Tmp+"/etc/ipsec.d", "/etc/ipsec.d").
|
|
||||||
Link(fst.Tmp+"/etc/issue", "/etc/issue").
|
|
||||||
Link(fst.Tmp+"/etc/kbd", "/etc/kbd").
|
|
||||||
Link(fst.Tmp+"/etc/libblockdev", "/etc/libblockdev").
|
|
||||||
Link(fst.Tmp+"/etc/locale.conf", "/etc/locale.conf").
|
|
||||||
Link(fst.Tmp+"/etc/localtime", "/etc/localtime").
|
|
||||||
Link(fst.Tmp+"/etc/login.defs", "/etc/login.defs").
|
|
||||||
Link(fst.Tmp+"/etc/lsb-release", "/etc/lsb-release").
|
|
||||||
Link(fst.Tmp+"/etc/lvm", "/etc/lvm").
|
|
||||||
Link(fst.Tmp+"/etc/machine-id", "/etc/machine-id").
|
|
||||||
Link(fst.Tmp+"/etc/man_db.conf", "/etc/man_db.conf").
|
|
||||||
Link(fst.Tmp+"/etc/modprobe.d", "/etc/modprobe.d").
|
|
||||||
Link(fst.Tmp+"/etc/modules-load.d", "/etc/modules-load.d").
|
|
||||||
Link("/proc/mounts", "/etc/mtab").
|
|
||||||
Link(fst.Tmp+"/etc/nanorc", "/etc/nanorc").
|
|
||||||
Link(fst.Tmp+"/etc/netgroup", "/etc/netgroup").
|
|
||||||
Link(fst.Tmp+"/etc/NetworkManager", "/etc/NetworkManager").
|
|
||||||
Link(fst.Tmp+"/etc/nix", "/etc/nix").
|
|
||||||
Link(fst.Tmp+"/etc/nixos", "/etc/nixos").
|
|
||||||
Link(fst.Tmp+"/etc/NIXOS", "/etc/NIXOS").
|
|
||||||
Link(fst.Tmp+"/etc/nscd.conf", "/etc/nscd.conf").
|
|
||||||
Link(fst.Tmp+"/etc/nsswitch.conf", "/etc/nsswitch.conf").
|
|
||||||
Link(fst.Tmp+"/etc/opensnitchd", "/etc/opensnitchd").
|
|
||||||
Link(fst.Tmp+"/etc/os-release", "/etc/os-release").
|
|
||||||
Link(fst.Tmp+"/etc/pam", "/etc/pam").
|
|
||||||
Link(fst.Tmp+"/etc/pam.d", "/etc/pam.d").
|
|
||||||
Link(fst.Tmp+"/etc/pipewire", "/etc/pipewire").
|
|
||||||
Link(fst.Tmp+"/etc/pki", "/etc/pki").
|
|
||||||
Link(fst.Tmp+"/etc/polkit-1", "/etc/polkit-1").
|
|
||||||
Link(fst.Tmp+"/etc/profile", "/etc/profile").
|
|
||||||
Link(fst.Tmp+"/etc/protocols", "/etc/protocols").
|
|
||||||
Link(fst.Tmp+"/etc/qemu", "/etc/qemu").
|
|
||||||
Link(fst.Tmp+"/etc/resolv.conf", "/etc/resolv.conf").
|
|
||||||
Link(fst.Tmp+"/etc/resolvconf.conf", "/etc/resolvconf.conf").
|
|
||||||
Link(fst.Tmp+"/etc/rpc", "/etc/rpc").
|
|
||||||
Link(fst.Tmp+"/etc/samba", "/etc/samba").
|
|
||||||
Link(fst.Tmp+"/etc/sddm.conf", "/etc/sddm.conf").
|
|
||||||
Link(fst.Tmp+"/etc/secureboot", "/etc/secureboot").
|
|
||||||
Link(fst.Tmp+"/etc/services", "/etc/services").
|
|
||||||
Link(fst.Tmp+"/etc/set-environment", "/etc/set-environment").
|
|
||||||
Link(fst.Tmp+"/etc/shadow", "/etc/shadow").
|
|
||||||
Link(fst.Tmp+"/etc/shells", "/etc/shells").
|
|
||||||
Link(fst.Tmp+"/etc/ssh", "/etc/ssh").
|
|
||||||
Link(fst.Tmp+"/etc/ssl", "/etc/ssl").
|
|
||||||
Link(fst.Tmp+"/etc/static", "/etc/static").
|
|
||||||
Link(fst.Tmp+"/etc/subgid", "/etc/subgid").
|
|
||||||
Link(fst.Tmp+"/etc/subuid", "/etc/subuid").
|
|
||||||
Link(fst.Tmp+"/etc/sudoers", "/etc/sudoers").
|
|
||||||
Link(fst.Tmp+"/etc/sysctl.d", "/etc/sysctl.d").
|
|
||||||
Link(fst.Tmp+"/etc/systemd", "/etc/systemd").
|
|
||||||
Link(fst.Tmp+"/etc/terminfo", "/etc/terminfo").
|
|
||||||
Link(fst.Tmp+"/etc/tmpfiles.d", "/etc/tmpfiles.d").
|
|
||||||
Link(fst.Tmp+"/etc/udev", "/etc/udev").
|
|
||||||
Link(fst.Tmp+"/etc/udisks2", "/etc/udisks2").
|
|
||||||
Link(fst.Tmp+"/etc/UPower", "/etc/UPower").
|
|
||||||
Link(fst.Tmp+"/etc/vconsole.conf", "/etc/vconsole.conf").
|
|
||||||
Link(fst.Tmp+"/etc/X11", "/etc/X11").
|
|
||||||
Link(fst.Tmp+"/etc/zfs", "/etc/zfs").
|
|
||||||
Link(fst.Tmp+"/etc/zinputrc", "/etc/zinputrc").
|
|
||||||
Link(fst.Tmp+"/etc/zoneinfo", "/etc/zoneinfo").
|
|
||||||
Link(fst.Tmp+"/etc/zprofile", "/etc/zprofile").
|
|
||||||
Link(fst.Tmp+"/etc/zshenv", "/etc/zshenv").
|
|
||||||
Link(fst.Tmp+"/etc/zshrc", "/etc/zshrc").
|
|
||||||
Tmpfs("/run/user", 4096, 0755).
|
|
||||||
Tmpfs("/run/user/65534", 8388608, 0700).
|
|
||||||
Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", sandbox.BindWritable).
|
|
||||||
Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
|
|
||||||
Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
|
||||||
Place("/etc/group", []byte("fortify:x:65534:\n")).
|
|
||||||
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0", 0).
|
|
||||||
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native", 0).
|
|
||||||
Place(fst.Tmp+"/pulse-cookie", nil).
|
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus", 0).
|
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
|
||||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package fst
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
@ -1,22 +1,22 @@
|
|||||||
package fst_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAppID(t *testing.T) {
|
func TestParseAppID(t *testing.T) {
|
||||||
t.Run("bad length", func(t *testing.T) {
|
t.Run("bad length", func(t *testing.T) {
|
||||||
if err := fst.ParseAppID(new(fst.ID), "meow"); !errors.Is(err, fst.ErrInvalidLength) {
|
if err := ParseAppID(new(ID), "meow"); !errors.Is(err, ErrInvalidLength) {
|
||||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, fst.ErrInvalidLength)
|
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, ErrInvalidLength)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad byte", func(t *testing.T) {
|
t.Run("bad byte", func(t *testing.T) {
|
||||||
wantErr := "invalid char '\\n' at byte 15"
|
wantErr := "invalid char '\\n' at byte 15"
|
||||||
if err := fst.ParseAppID(new(fst.ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
|
if err := ParseAppID(new(ID), "02bc7f8936b2af6\n\ne2535cd71ef0bb7"); err == nil || err.Error() != wantErr {
|
||||||
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
|
t.Errorf("ParseAppID: error = %v, wantErr = %v", err, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -30,30 +30,30 @@ func TestParseAppID(t *testing.T) {
|
|||||||
|
|
||||||
func FuzzParseAppID(f *testing.F) {
|
func FuzzParseAppID(f *testing.F) {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
id := new(fst.ID)
|
id := new(ID)
|
||||||
if err := fst.NewAppID(id); err != nil {
|
if err := NewAppID(id); err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
|
f.Add(id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15])
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
|
f.Fuzz(func(t *testing.T, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 byte) {
|
||||||
testParseAppID(t, &fst.ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
|
testParseAppID(t, &ID{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseAppIDWithRandom(t *testing.T) {
|
func testParseAppIDWithRandom(t *testing.T) {
|
||||||
id := new(fst.ID)
|
id := new(ID)
|
||||||
if err := fst.NewAppID(id); err != nil {
|
if err := NewAppID(id); err != nil {
|
||||||
t.Fatalf("cannot generate app ID: %v", err)
|
t.Fatalf("cannot generate app ID: %v", err)
|
||||||
}
|
}
|
||||||
testParseAppID(t, id)
|
testParseAppID(t, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseAppID(t *testing.T, id *fst.ID) {
|
func testParseAppID(t *testing.T, id *ID) {
|
||||||
s := id.String()
|
s := id.String()
|
||||||
got := new(fst.ID)
|
got := new(ID)
|
||||||
if err := fst.ParseAppID(got, s); err != nil {
|
if err := ParseAppID(got, s); err != nil {
|
||||||
t.Fatalf("cannot parse app ID: %v", err)
|
t.Fatalf("cannot parse app ID: %v", err)
|
||||||
}
|
}
|
||||||
|
|
189
internal/app/instance/common/container.go
Normal file
189
internal/app/instance/common/container.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"maps"
|
||||||
|
"path"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// in practice there should be less than 30 entries added by the runtime;
|
||||||
|
// allocating slightly more as a margin for future expansion
|
||||||
|
const preallocateOpsCount = 1 << 5
|
||||||
|
|
||||||
|
// NewContainer initialises [sandbox.Params] via [fst.ContainerConfig].
|
||||||
|
// Note that remaining container setup must be queued by the caller.
|
||||||
|
func NewContainer(s *fst.ContainerConfig, os sys.State, uid, gid *int) (*sandbox.Params, map[string]string, error) {
|
||||||
|
if s == nil {
|
||||||
|
return nil, nil, syscall.EBADE
|
||||||
|
}
|
||||||
|
|
||||||
|
container := &sandbox.Params{
|
||||||
|
Hostname: s.Hostname,
|
||||||
|
Seccomp: s.Seccomp,
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ops := make(sandbox.Ops, 0, preallocateOpsCount+len(s.Filesystem)+len(s.Link)+len(s.Cover))
|
||||||
|
container.Ops = &ops
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Multiarch {
|
||||||
|
container.Seccomp |= seccomp.FilterMultiarch
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Devel {
|
||||||
|
container.Flags |= sandbox.FAllowDevel
|
||||||
|
}
|
||||||
|
if s.Userns {
|
||||||
|
container.Flags |= sandbox.FAllowUserns
|
||||||
|
}
|
||||||
|
if s.Net {
|
||||||
|
container.Flags |= sandbox.FAllowNet
|
||||||
|
}
|
||||||
|
if s.Tty {
|
||||||
|
container.Flags |= sandbox.FAllowTTY
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.MapRealUID {
|
||||||
|
/* some programs fail to connect to dbus session running as a different uid
|
||||||
|
so this workaround is introduced to map priv-side caller uid in container */
|
||||||
|
container.Uid = os.Getuid()
|
||||||
|
*uid = container.Uid
|
||||||
|
container.Gid = os.Getgid()
|
||||||
|
*gid = container.Gid
|
||||||
|
} else {
|
||||||
|
*uid = sandbox.OverflowUid()
|
||||||
|
*gid = sandbox.OverflowGid()
|
||||||
|
}
|
||||||
|
|
||||||
|
container.
|
||||||
|
Proc("/proc").
|
||||||
|
Tmpfs(fst.Tmp, 1<<12, 0755)
|
||||||
|
|
||||||
|
if !s.Device {
|
||||||
|
container.Dev("/dev").Mqueue("/dev/mqueue")
|
||||||
|
} else {
|
||||||
|
container.Bind("/dev", "/dev", sandbox.BindWritable|sandbox.BindDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* retrieve paths and hide them if they're made available in the sandbox;
|
||||||
|
this feature tries to improve user experience of permissive defaults, and
|
||||||
|
to warn about issues in custom configuration; it is NOT a security feature
|
||||||
|
and should not be treated as such, ALWAYS be careful with what you bind */
|
||||||
|
var hidePaths []string
|
||||||
|
sc := os.Paths()
|
||||||
|
hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
|
||||||
|
_, systemBusAddr := dbus.Address()
|
||||||
|
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else {
|
||||||
|
// there is usually only one, do not preallocate
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.Method != "unix" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pair := range entry.Values {
|
||||||
|
if pair[0] == "path" {
|
||||||
|
if path.IsAbs(pair[1]) {
|
||||||
|
// get parent dir of socket
|
||||||
|
dir := path.Dir(pair[1])
|
||||||
|
if dir == "." || dir == "/" {
|
||||||
|
os.Printf("dbus socket %q is in an unusual location", pair[1])
|
||||||
|
}
|
||||||
|
hidePaths = append(hidePaths, dir)
|
||||||
|
} else {
|
||||||
|
os.Printf("dbus socket %q is not absolute", pair[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hidePathMatch := make([]bool, len(hidePaths))
|
||||||
|
for i := range hidePaths {
|
||||||
|
if err := evalSymlinks(os, &hidePaths[i]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range s.Filesystem {
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.IsAbs(c.Src) {
|
||||||
|
return nil, nil, fmt.Errorf("src path %q is not absolute", c.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := c.Dst
|
||||||
|
if c.Dst == "" {
|
||||||
|
dest = c.Src
|
||||||
|
} else if !path.IsAbs(dest) {
|
||||||
|
return nil, nil, fmt.Errorf("dst path %q is not absolute", dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcH := c.Src
|
||||||
|
if err := evalSymlinks(os, &srcH); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range hidePaths {
|
||||||
|
// skip matched entries
|
||||||
|
if hidePathMatch[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
} else if ok {
|
||||||
|
hidePathMatch[i] = true
|
||||||
|
os.Printf("hiding paths from %q", c.Src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags int
|
||||||
|
if c.Write {
|
||||||
|
flags |= sandbox.BindWritable
|
||||||
|
}
|
||||||
|
if c.Device {
|
||||||
|
flags |= sandbox.BindDevice | sandbox.BindWritable
|
||||||
|
}
|
||||||
|
if !c.Must {
|
||||||
|
flags |= sandbox.BindOptional
|
||||||
|
}
|
||||||
|
container.Bind(c.Src, dest, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cover matched paths
|
||||||
|
for i, ok := range hidePathMatch {
|
||||||
|
if ok {
|
||||||
|
container.Tmpfs(hidePaths[i], 1<<13, 0755)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range s.Link {
|
||||||
|
container.Link(l[0], l[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return container, maps.Clone(s.Env), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalSymlinks(os sys.State, v *string) error {
|
||||||
|
if p, err := os.EvalSymlinks(*v); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.Printf("path %q does not yet exist", *v)
|
||||||
|
} else {
|
||||||
|
*v = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fst
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
@ -1,4 +1,4 @@
|
|||||||
package fst
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
17
internal/app/instance/errors.go
Normal file
17
internal/app/instance/errors.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrintRunStateErr(whence int, rs *app.RunState, runErr error) (code int) {
|
||||||
|
switch whence {
|
||||||
|
case ISetuid:
|
||||||
|
return setuid.PrintRunStateErr(rs, runErr)
|
||||||
|
default:
|
||||||
|
panic(syscall.EINVAL)
|
||||||
|
}
|
||||||
|
}
|
33
internal/app/instance/new.go
Normal file
33
internal/app/instance/new.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Package instance exposes cross-package implementation details and provides constructors for builtin implementations.
|
||||||
|
package instance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ISetuid = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(whence int, ctx context.Context, os sys.State) (app.App, error) {
|
||||||
|
switch whence {
|
||||||
|
case ISetuid:
|
||||||
|
return setuid.New(ctx, os)
|
||||||
|
default:
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustNew(whence int, ctx context.Context, os sys.State) app.App {
|
||||||
|
a, err := New(whence, ctx, os)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("cannot create app: %v", err)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
6
internal/app/instance/shim.go
Normal file
6
internal/app/instance/shim.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package instance
|
||||||
|
|
||||||
|
import "git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
|
||||||
|
|
||||||
|
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||||
|
func ShimMain() { setuid.ShimMain() }
|
74
internal/app/internal/setuid/app.go
Normal file
74
internal/app/internal/setuid/app.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package setuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(ctx context.Context, os sys.State) (App, error) {
|
||||||
|
a := new(app)
|
||||||
|
a.sys = os
|
||||||
|
a.ctx = ctx
|
||||||
|
|
||||||
|
id := new(ID)
|
||||||
|
err := NewAppID(id)
|
||||||
|
a.id = newID(id)
|
||||||
|
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type app struct {
|
||||||
|
id *stringPair[ID]
|
||||||
|
sys sys.State
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
*outcome
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) ID() ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.unwrap() }
|
||||||
|
|
||||||
|
func (a *app) String() string {
|
||||||
|
if a == nil {
|
||||||
|
return "(invalid app)"
|
||||||
|
}
|
||||||
|
|
||||||
|
a.mu.RLock()
|
||||||
|
defer a.mu.RUnlock()
|
||||||
|
|
||||||
|
if a.outcome != nil {
|
||||||
|
if a.outcome.user.uid == nil {
|
||||||
|
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("(unsealed app %s)", a.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) Seal(config *fst.Config) (SealedApp, error) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
|
if a.outcome != nil {
|
||||||
|
panic("app sealed twice")
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmsg.WrapError(ErrConfig,
|
||||||
|
"attempted to seal app with nil config")
|
||||||
|
}
|
||||||
|
|
||||||
|
seal := new(outcome)
|
||||||
|
seal.id = a.id
|
||||||
|
err := seal.finalise(a.ctx, a.sys, config)
|
||||||
|
if err == nil {
|
||||||
|
a.outcome = seal
|
||||||
|
}
|
||||||
|
return seal, err
|
||||||
|
}
|
145
internal/app/internal/setuid/app_nixos_test.go
Normal file
145
internal/app/internal/setuid/app_nixos_test.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package setuid_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testCasesNixos = []sealTestCase{
|
||||||
|
{
|
||||||
|
"nixos chromium direct wayland", new(stubNixOS),
|
||||||
|
&fst.Config{
|
||||||
|
ID: "org.chromium.Chromium",
|
||||||
|
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
|
||||||
|
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
||||||
|
|
||||||
|
Container: &fst.ContainerConfig{
|
||||||
|
Userns: true, Net: true, MapRealUID: true, Env: nil, AutoEtc: true,
|
||||||
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
|
{Src: "/bin", Must: true}, {Src: "/usr/bin", Must: true},
|
||||||
|
{Src: "/nix/store", Must: true}, {Src: "/run/current-system", Must: true},
|
||||||
|
{Src: "/sys/block"}, {Src: "/sys/bus"}, {Src: "/sys/class"}, {Src: "/sys/dev"}, {Src: "/sys/devices"},
|
||||||
|
{Src: "/run/opengl-driver", Must: true}, {Src: "/dev/dri", Device: true},
|
||||||
|
},
|
||||||
|
Cover: []string{"/var/run/nscd"},
|
||||||
|
},
|
||||||
|
SystemBus: &dbus.Config{
|
||||||
|
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
SessionBus: &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5", "org.kde.kwalletd6",
|
||||||
|
},
|
||||||
|
Own: []string{
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*",
|
||||||
|
},
|
||||||
|
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
DirectWayland: true,
|
||||||
|
|
||||||
|
Username: "u0_a1",
|
||||||
|
Data: "/var/lib/persist/module/fortify/0/1",
|
||||||
|
Identity: 1, Groups: []string{},
|
||||||
|
},
|
||||||
|
app.ID{
|
||||||
|
0x8e, 0x2c, 0x76, 0xb0,
|
||||||
|
0x66, 0xda, 0xbe, 0x57,
|
||||||
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
|
},
|
||||||
|
system.New(1000001).
|
||||||
|
Ensure("/tmp/fortify.1971", 0711).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
|
||||||
|
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
|
||||||
|
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
|
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
|
||||||
|
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
||||||
|
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
||||||
|
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
|
Ephemeral(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
|
||||||
|
MustProxyDBus("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5", "org.kde.kwalletd6",
|
||||||
|
},
|
||||||
|
Own: []string{
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*",
|
||||||
|
},
|
||||||
|
Call: map[string]string{}, Broadcast: map[string]string{},
|
||||||
|
Filter: true,
|
||||||
|
}, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
}).
|
||||||
|
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
|
||||||
|
UpdatePerm("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
|
||||||
|
&sandbox.Params{
|
||||||
|
Uid: 1971,
|
||||||
|
Gid: 100,
|
||||||
|
Flags: sandbox.FAllowNet | sandbox.FAllowUserns,
|
||||||
|
Dir: "/var/lib/persist/module/fortify/0/1",
|
||||||
|
Path: "/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start",
|
||||||
|
Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
|
||||||
|
Env: []string{
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
|
||||||
|
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
||||||
|
"HOME=/var/lib/persist/module/fortify/0/1",
|
||||||
|
"PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
|
||||||
|
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
|
||||||
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
|
"TERM=xterm-256color",
|
||||||
|
"USER=u0_a1",
|
||||||
|
"WAYLAND_DISPLAY=wayland-0",
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/1971",
|
||||||
|
"XDG_SESSION_CLASS=user",
|
||||||
|
"XDG_SESSION_TYPE=tty",
|
||||||
|
},
|
||||||
|
Ops: new(sandbox.Ops).
|
||||||
|
Proc("/proc").
|
||||||
|
Tmpfs(fst.Tmp, 4096, 0755).
|
||||||
|
Dev("/dev").Mqueue("/dev/mqueue").
|
||||||
|
Bind("/bin", "/bin", 0).
|
||||||
|
Bind("/usr/bin", "/usr/bin", 0).
|
||||||
|
Bind("/nix/store", "/nix/store", 0).
|
||||||
|
Bind("/run/current-system", "/run/current-system", 0).
|
||||||
|
Bind("/sys/block", "/sys/block", sandbox.BindOptional).
|
||||||
|
Bind("/sys/bus", "/sys/bus", sandbox.BindOptional).
|
||||||
|
Bind("/sys/class", "/sys/class", sandbox.BindOptional).
|
||||||
|
Bind("/sys/dev", "/sys/dev", sandbox.BindOptional).
|
||||||
|
Bind("/sys/devices", "/sys/devices", sandbox.BindOptional).
|
||||||
|
Bind("/run/opengl-driver", "/run/opengl-driver", 0).
|
||||||
|
Bind("/dev/dri", "/dev/dri", sandbox.BindDevice|sandbox.BindWritable|sandbox.BindOptional).
|
||||||
|
Etc("/etc", "8e2c76b066dabe574cf073bdb46eb5c1").
|
||||||
|
Tmpfs("/run/user", 4096, 0755).
|
||||||
|
Tmpfs("/run/user/1971", 8388608, 0700).
|
||||||
|
Bind("/tmp/fortify.1971/tmpdir/1", "/tmp", sandbox.BindWritable).
|
||||||
|
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", sandbox.BindWritable).
|
||||||
|
Place("/etc/passwd", []byte("u0_a1:x:1971:100:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||||
|
Place("/etc/group", []byte("fortify:x:100:\n")).
|
||||||
|
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0", 0).
|
||||||
|
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native", 0).
|
||||||
|
Place(fst.Tmp+"/pulse-cookie", nil).
|
||||||
|
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus", 0).
|
||||||
|
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
||||||
|
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
216
internal/app/internal/setuid/app_pd_test.go
Normal file
216
internal/app/internal/setuid/app_pd_test.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package setuid_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testCasesPd = []sealTestCase{
|
||||||
|
{
|
||||||
|
"nixos permissive defaults no enablements", new(stubNixOS),
|
||||||
|
&fst.Config{Username: "chronos", Data: "/home/chronos"},
|
||||||
|
app.ID{
|
||||||
|
0x4a, 0x45, 0x0b, 0x65,
|
||||||
|
0x96, 0xd7, 0xbc, 0x15,
|
||||||
|
0xbd, 0x01, 0x78, 0x0e,
|
||||||
|
0xb9, 0xa6, 0x07, 0xac,
|
||||||
|
},
|
||||||
|
system.New(1000000).
|
||||||
|
Ensure("/tmp/fortify.1971", 0711).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
|
||||||
|
&sandbox.Params{
|
||||||
|
Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
|
||||||
|
Dir: "/home/chronos",
|
||||||
|
Path: "/run/current-system/sw/bin/zsh",
|
||||||
|
Args: []string{"/run/current-system/sw/bin/zsh"},
|
||||||
|
Env: []string{
|
||||||
|
"HOME=/home/chronos",
|
||||||
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
|
"TERM=xterm-256color",
|
||||||
|
"USER=chronos",
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/65534",
|
||||||
|
"XDG_SESSION_CLASS=user",
|
||||||
|
"XDG_SESSION_TYPE=tty",
|
||||||
|
},
|
||||||
|
Ops: new(sandbox.Ops).
|
||||||
|
Proc("/proc").
|
||||||
|
Tmpfs(fst.Tmp, 4096, 0755).
|
||||||
|
Dev("/dev").Mqueue("/dev/mqueue").
|
||||||
|
Bind("/bin", "/bin", sandbox.BindWritable).
|
||||||
|
Bind("/boot", "/boot", sandbox.BindWritable).
|
||||||
|
Bind("/home", "/home", sandbox.BindWritable).
|
||||||
|
Bind("/lib", "/lib", sandbox.BindWritable).
|
||||||
|
Bind("/lib64", "/lib64", sandbox.BindWritable).
|
||||||
|
Bind("/nix", "/nix", sandbox.BindWritable).
|
||||||
|
Bind("/root", "/root", sandbox.BindWritable).
|
||||||
|
Bind("/run", "/run", sandbox.BindWritable).
|
||||||
|
Bind("/srv", "/srv", sandbox.BindWritable).
|
||||||
|
Bind("/sys", "/sys", sandbox.BindWritable).
|
||||||
|
Bind("/usr", "/usr", sandbox.BindWritable).
|
||||||
|
Bind("/var", "/var", sandbox.BindWritable).
|
||||||
|
Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
||||||
|
Tmpfs("/run/user/1971", 8192, 0755).
|
||||||
|
Tmpfs("/run/dbus", 8192, 0755).
|
||||||
|
Etc("/etc", "4a450b6596d7bc15bd01780eb9a607ac").
|
||||||
|
Tmpfs("/run/user", 4096, 0755).
|
||||||
|
Tmpfs("/run/user/65534", 8388608, 0700).
|
||||||
|
Bind("/tmp/fortify.1971/tmpdir/0", "/tmp", sandbox.BindWritable).
|
||||||
|
Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
|
||||||
|
Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
|
Place("/etc/group", []byte("fortify:x:65534:\n")).
|
||||||
|
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nixos permissive defaults chromium", new(stubNixOS),
|
||||||
|
&fst.Config{
|
||||||
|
ID: "org.chromium.Chromium",
|
||||||
|
Args: []string{"zsh", "-c", "exec chromium "},
|
||||||
|
Identity: 9,
|
||||||
|
Groups: []string{"video"},
|
||||||
|
Username: "chronos",
|
||||||
|
Data: "/home/chronos",
|
||||||
|
SessionBus: &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager",
|
||||||
|
},
|
||||||
|
Own: []string{
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*",
|
||||||
|
},
|
||||||
|
Call: map[string]string{
|
||||||
|
"org.freedesktop.portal.*": "*",
|
||||||
|
},
|
||||||
|
Broadcast: map[string]string{
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
SystemBus: &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
},
|
||||||
|
Enablements: system.EWayland | system.EDBus | system.EPulse,
|
||||||
|
},
|
||||||
|
app.ID{
|
||||||
|
0xeb, 0xf0, 0x83, 0xd1,
|
||||||
|
0xb1, 0x75, 0x91, 0x17,
|
||||||
|
0x82, 0xd4, 0x13, 0x36,
|
||||||
|
0x9b, 0x64, 0xce, 0x7c,
|
||||||
|
},
|
||||||
|
system.New(1000009).
|
||||||
|
Ensure("/tmp/fortify.1971", 0711).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir", acl.Execute).
|
||||||
|
Ensure("/tmp/fortify.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
|
||||||
|
Ephemeral(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c", 0711).
|
||||||
|
Wayland(new(*os.File), "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
||||||
|
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
|
||||||
|
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
|
||||||
|
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
||||||
|
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
|
||||||
|
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
|
||||||
|
MustProxyDBus("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager",
|
||||||
|
},
|
||||||
|
Own: []string{
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*",
|
||||||
|
},
|
||||||
|
Call: map[string]string{
|
||||||
|
"org.freedesktop.portal.*": "*",
|
||||||
|
},
|
||||||
|
Broadcast: map[string]string{
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
}, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
|
||||||
|
Talk: []string{
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower",
|
||||||
|
},
|
||||||
|
Filter: true,
|
||||||
|
}).
|
||||||
|
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
|
||||||
|
UpdatePerm("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
|
||||||
|
&sandbox.Params{
|
||||||
|
Flags: sandbox.FAllowNet | sandbox.FAllowUserns | sandbox.FAllowTTY,
|
||||||
|
Dir: "/home/chronos",
|
||||||
|
Path: "/run/current-system/sw/bin/zsh",
|
||||||
|
Args: []string{"zsh", "-c", "exec chromium "},
|
||||||
|
Env: []string{
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
|
||||||
|
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
|
||||||
|
"HOME=/home/chronos",
|
||||||
|
"PULSE_COOKIE=" + fst.Tmp + "/pulse-cookie",
|
||||||
|
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
|
||||||
|
"SHELL=/run/current-system/sw/bin/zsh",
|
||||||
|
"TERM=xterm-256color",
|
||||||
|
"USER=chronos",
|
||||||
|
"WAYLAND_DISPLAY=wayland-0",
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/65534",
|
||||||
|
"XDG_SESSION_CLASS=user",
|
||||||
|
"XDG_SESSION_TYPE=tty",
|
||||||
|
},
|
||||||
|
Ops: new(sandbox.Ops).
|
||||||
|
Proc("/proc").
|
||||||
|
Tmpfs(fst.Tmp, 4096, 0755).
|
||||||
|
Dev("/dev").Mqueue("/dev/mqueue").
|
||||||
|
Bind("/bin", "/bin", sandbox.BindWritable).
|
||||||
|
Bind("/boot", "/boot", sandbox.BindWritable).
|
||||||
|
Bind("/home", "/home", sandbox.BindWritable).
|
||||||
|
Bind("/lib", "/lib", sandbox.BindWritable).
|
||||||
|
Bind("/lib64", "/lib64", sandbox.BindWritable).
|
||||||
|
Bind("/nix", "/nix", sandbox.BindWritable).
|
||||||
|
Bind("/root", "/root", sandbox.BindWritable).
|
||||||
|
Bind("/run", "/run", sandbox.BindWritable).
|
||||||
|
Bind("/srv", "/srv", sandbox.BindWritable).
|
||||||
|
Bind("/sys", "/sys", sandbox.BindWritable).
|
||||||
|
Bind("/usr", "/usr", sandbox.BindWritable).
|
||||||
|
Bind("/var", "/var", sandbox.BindWritable).
|
||||||
|
Bind("/dev/dri", "/dev/dri", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
||||||
|
Bind("/dev/kvm", "/dev/kvm", sandbox.BindWritable|sandbox.BindDevice|sandbox.BindOptional).
|
||||||
|
Tmpfs("/run/user/1971", 8192, 0755).
|
||||||
|
Tmpfs("/run/dbus", 8192, 0755).
|
||||||
|
Etc("/etc", "ebf083d1b175911782d413369b64ce7c").
|
||||||
|
Tmpfs("/run/user", 4096, 0755).
|
||||||
|
Tmpfs("/run/user/65534", 8388608, 0700).
|
||||||
|
Bind("/tmp/fortify.1971/tmpdir/9", "/tmp", sandbox.BindWritable).
|
||||||
|
Bind("/home/chronos", "/home/chronos", sandbox.BindWritable).
|
||||||
|
Place("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
|
Place("/etc/group", []byte("fortify:x:65534:\n")).
|
||||||
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/65534/wayland-0", 0).
|
||||||
|
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native", 0).
|
||||||
|
Place(fst.Tmp+"/pulse-cookie", nil).
|
||||||
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus", 0).
|
||||||
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket", 0).
|
||||||
|
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app_test
|
package setuid_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fs methods are not implemented using a real FS
|
// fs methods are not implemented using a real FS
|
||||||
@ -125,8 +125,8 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Paths() fst.Paths {
|
func (s *stubNixOS) Paths() app.Paths {
|
||||||
return fst.Paths{
|
return app.Paths{
|
||||||
SharePath: "/tmp/fortify.1971",
|
SharePath: "/tmp/fortify.1971",
|
||||||
RuntimePath: "/run/user/1971",
|
RuntimePath: "/run/user/1971",
|
||||||
RunDirPath: "/run/user/1971/fortify",
|
RunDirPath: "/run/user/1971/fortify",
|
@ -1,4 +1,4 @@
|
|||||||
package app_test
|
package setuid_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/internal/setuid"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
@ -18,7 +19,7 @@ type sealTestCase struct {
|
|||||||
name string
|
name string
|
||||||
os sys.State
|
os sys.State
|
||||||
config *fst.Config
|
config *fst.Config
|
||||||
id fst.ID
|
id app.ID
|
||||||
wantSys *system.I
|
wantSys *system.I
|
||||||
wantContainer *sandbox.Params
|
wantContainer *sandbox.Params
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ func TestApp(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
a := app.NewWithID(tc.id, tc.os)
|
a := setuid.NewWithID(tc.id, tc.os)
|
||||||
var (
|
var (
|
||||||
gotSys *system.I
|
gotSys *system.I
|
||||||
gotContainer *sandbox.Params
|
gotContainer *sandbox.Params
|
||||||
@ -38,7 +39,7 @@ func TestApp(t *testing.T) {
|
|||||||
t.Errorf("Seal: error = %v", err)
|
t.Errorf("Seal: error = %v", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
gotSys, gotContainer = app.AppIParams(a, sa)
|
gotSys, gotContainer = setuid.AppIParams(a, sa)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
return
|
return
|
@ -1,14 +1,16 @@
|
|||||||
package app
|
package setuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrintRunStateErr(rs *fst.RunState, runErr error) {
|
func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
||||||
|
code = rs.ExitStatus()
|
||||||
|
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
if rs.Time == nil {
|
if rs.Time == nil {
|
||||||
fmsg.PrintBaseError(runErr, "cannot start app:")
|
fmsg.PrintBaseError(runErr, "cannot start app:")
|
||||||
@ -49,8 +51,8 @@ func PrintRunStateErr(rs *fst.RunState, runErr error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs.ExitCode == 0 {
|
if code == 0 {
|
||||||
rs.ExitCode = 126
|
code = 126
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,13 +99,14 @@ func PrintRunStateErr(rs *fst.RunState, runErr error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if rs.ExitCode == 0 {
|
if code == 0 {
|
||||||
rs.ExitCode = 128
|
code = 128
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rs.WaitErr != nil {
|
if rs.WaitErr != nil {
|
||||||
log.Println("inner wait failed:", rs.WaitErr)
|
fmsg.Verbosef("wait: %v", rs.WaitErr)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save
|
// StateStoreError is returned for a failed state save
|
||||||
@ -121,7 +124,7 @@ type StateStoreError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// save saves arbitrary errors in [StateStoreError] once.
|
// save saves arbitrary errors in [StateStoreError] once.
|
||||||
func (e *StateStoreError) save(errs []error) {
|
func (e *StateStoreError) save(errs ...error) {
|
||||||
if len(errs) == 0 || e.Err != nil {
|
if len(errs) == 0 || e.Err != nil {
|
||||||
panic("invalid call to save")
|
panic("invalid call to save")
|
||||||
}
|
}
|
@ -1,20 +1,20 @@
|
|||||||
package app
|
package setuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewWithID(id fst.ID, os sys.State) fst.App {
|
func NewWithID(id ID, os sys.State) App {
|
||||||
a := new(app)
|
a := new(app)
|
||||||
a.id = newID(&id)
|
a.id = newID(&id)
|
||||||
a.sys = os
|
a.sys = os
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppIParams(a fst.App, sa fst.SealedApp) (*system.I, *sandbox.Params) {
|
func AppIParams(a App, sa SealedApp) (*system.I, *sandbox.Params) {
|
||||||
v := a.(*app)
|
v := a.(*app)
|
||||||
seal := sa.(*outcome)
|
seal := sa.(*outcome)
|
||||||
if v.outcome != seal || v.id != seal.id {
|
if v.outcome != seal || v.id != seal.id {
|
195
internal/app/internal/setuid/process.go
Normal file
195
internal/app/internal/setuid/process.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package setuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shimWaitTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
func (seal *outcome) Run(rs *RunState) error {
|
||||||
|
if !seal.f.CompareAndSwap(false, true) {
|
||||||
|
// run does much more than just starting a process; calling it twice, even if the first call fails, will result
|
||||||
|
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
|
||||||
|
// other Run a chance to return
|
||||||
|
return errors.New("outcome: attempted to run twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs == nil {
|
||||||
|
panic("invalid state")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read comp value early to allow for early failure
|
||||||
|
fsuPath := internal.MustFsuPath()
|
||||||
|
|
||||||
|
if err := seal.sys.Commit(seal.ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
store := state.NewMulti(seal.runDirPath)
|
||||||
|
deferredStoreFunc := func(c state.Cursor) error { return nil } // noop until state in store
|
||||||
|
defer func() {
|
||||||
|
var revertErr error
|
||||||
|
storeErr := new(StateStoreError)
|
||||||
|
storeErr.Inner, storeErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
||||||
|
revertErr = func() error {
|
||||||
|
storeErr.InnerErr = deferredStoreFunc(c)
|
||||||
|
|
||||||
|
var rt system.Enablement
|
||||||
|
ec := system.Process
|
||||||
|
if states, err := c.Load(); err != nil {
|
||||||
|
// revert per-process state here to limit damage
|
||||||
|
storeErr.OpErr = err
|
||||||
|
return seal.sys.Revert((*system.Criteria)(&ec))
|
||||||
|
} else {
|
||||||
|
if l := len(states); l == 0 {
|
||||||
|
ec |= system.User
|
||||||
|
} else {
|
||||||
|
fmsg.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate enablements of remaining launchers
|
||||||
|
for i, s := range states {
|
||||||
|
if s.Config != nil {
|
||||||
|
rt |= s.Config.Enablements
|
||||||
|
} else {
|
||||||
|
log.Printf("state entry %d does not contain config", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
||||||
|
if fmsg.Load() {
|
||||||
|
if ec > 0 {
|
||||||
|
fmsg.Verbose("reverting operations scope", system.TypeString(ec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seal.sys.Revert((*system.Criteria)(&ec))
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
storeErr.save(revertErr, store.Close())
|
||||||
|
rs.RevertErr = storeErr.equiv("error during cleanup:")
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(seal.ctx)
|
||||||
|
defer cancel()
|
||||||
|
cmd := exec.CommandContext(ctx, fsuPath)
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
cmd.Dir = "/" // container init enters final working directory
|
||||||
|
// shim runs in the same session as monitor; see shim.go for behaviour
|
||||||
|
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) }
|
||||||
|
|
||||||
|
var e *gob.Encoder
|
||||||
|
if fd, encoder, err := sandbox.Setup(&cmd.ExtraFiles); err != nil {
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"cannot create shim setup pipe:")
|
||||||
|
} else {
|
||||||
|
e = encoder
|
||||||
|
cmd.Env = []string{
|
||||||
|
// passed through to shim by fsu
|
||||||
|
shimEnv + "=" + strconv.Itoa(fd),
|
||||||
|
// interpreted by fsu
|
||||||
|
"FORTIFY_APP_ID=" + seal.user.aid.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(seal.user.supp) > 0 {
|
||||||
|
fmsg.Verbosef("attaching supplementary group ids %s", seal.user.supp)
|
||||||
|
// interpreted by fsu
|
||||||
|
cmd.Env = append(cmd.Env, "FORTIFY_GROUPS="+strings.Join(seal.user.supp, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmsg.Verbosef("setuid helper at %s", fsuPath)
|
||||||
|
fmsg.Suspend()
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"cannot start setuid wrapper:")
|
||||||
|
}
|
||||||
|
rs.SetStart()
|
||||||
|
|
||||||
|
// this prevents blocking forever on an early failure
|
||||||
|
waitErr, setupErr := make(chan error, 1), make(chan error, 1)
|
||||||
|
go func() { waitErr <- cmd.Wait(); cancel() }()
|
||||||
|
go func() { setupErr <- e.Encode(&shimParams{os.Getpid(), seal.container, seal.user.data, fmsg.Load()}) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-setupErr:
|
||||||
|
if err != nil {
|
||||||
|
fmsg.Resume()
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"cannot transmit shim config:")
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmsg.Resume()
|
||||||
|
return fmsg.WrapError(syscall.ECANCELED,
|
||||||
|
"shim setup canceled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// returned after blocking on waitErr
|
||||||
|
var earlyStoreErr = new(StateStoreError)
|
||||||
|
{
|
||||||
|
// shim accepted setup payload, create process state
|
||||||
|
sd := state.State{
|
||||||
|
ID: seal.id.unwrap(),
|
||||||
|
PID: cmd.Process.Pid,
|
||||||
|
Time: *rs.Time,
|
||||||
|
}
|
||||||
|
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
||||||
|
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// state in store at this point, destroy defunct state entry on return
|
||||||
|
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
||||||
|
|
||||||
|
waitTimeout := make(chan struct{})
|
||||||
|
go func() { <-seal.ctx.Done(); time.Sleep(shimWaitTimeout); close(waitTimeout) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case rs.WaitErr = <-waitErr:
|
||||||
|
rs.WaitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
|
if fmsg.Load() {
|
||||||
|
switch {
|
||||||
|
case rs.Exited():
|
||||||
|
fmsg.Verbosef("process %d exited with code %d", cmd.Process.Pid, rs.ExitStatus())
|
||||||
|
case rs.CoreDump():
|
||||||
|
fmsg.Verbosef("process %d dumped core", cmd.Process.Pid)
|
||||||
|
case rs.Signaled():
|
||||||
|
fmsg.Verbosef("process %d got %s", cmd.Process.Pid, rs.Signal())
|
||||||
|
default:
|
||||||
|
fmsg.Verbosef("process %d exited with status %#x", cmd.Process.Pid, rs.WaitStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-waitTimeout:
|
||||||
|
rs.WaitErr = syscall.ETIMEDOUT
|
||||||
|
fmsg.Resume()
|
||||||
|
log.Printf("process %d did not terminate", cmd.Process.Pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmsg.Resume()
|
||||||
|
if seal.sync != nil {
|
||||||
|
if err := seal.sync.Close(); err != nil {
|
||||||
|
log.Printf("cannot close wayland security context: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if seal.dbusMsg != nil {
|
||||||
|
seal.dbusMsg()
|
||||||
|
}
|
||||||
|
|
||||||
|
return earlyStoreErr.equiv("cannot save process state:")
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app
|
package setuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -20,6 +20,8 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"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/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/instance/common"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
@ -64,7 +66,7 @@ var posixUsername = regexp.MustCompilePOSIX("^[a-z_]([A-Za-z0-9_-]{0,31}|[A-Za-z
|
|||||||
// outcome stores copies of various parts of [fst.Config]
|
// outcome stores copies of various parts of [fst.Config]
|
||||||
type outcome struct {
|
type outcome struct {
|
||||||
// copied from initialising [app]
|
// copied from initialising [app]
|
||||||
id *stringPair[fst.ID]
|
id *stringPair[ID]
|
||||||
// copied from [sys.State] response
|
// copied from [sys.State] response
|
||||||
runDirPath string
|
runDirPath string
|
||||||
|
|
||||||
@ -85,6 +87,53 @@ type outcome struct {
|
|||||||
f atomic.Bool
|
f atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shareHost holds optional share directory state that must not be accessed directly
|
||||||
|
type shareHost struct {
|
||||||
|
// whether XDG_RUNTIME_DIR is used post fsu
|
||||||
|
useRuntimeDir bool
|
||||||
|
// process-specific directory in tmpdir, empty if unused
|
||||||
|
sharePath string
|
||||||
|
// process-specific directory in XDG_RUNTIME_DIR, empty if unused
|
||||||
|
runtimeSharePath string
|
||||||
|
|
||||||
|
seal *outcome
|
||||||
|
sc Paths
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureRuntimeDir must be called if direct access to paths within XDG_RUNTIME_DIR is required
|
||||||
|
func (share *shareHost) ensureRuntimeDir() {
|
||||||
|
if share.useRuntimeDir {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
share.useRuntimeDir = true
|
||||||
|
share.seal.sys.Ensure(share.sc.RunDirPath, 0700)
|
||||||
|
share.seal.sys.UpdatePermType(system.User, share.sc.RunDirPath, acl.Execute)
|
||||||
|
share.seal.sys.Ensure(share.sc.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
|
||||||
|
share.seal.sys.UpdatePermType(system.User, share.sc.RuntimePath, acl.Execute)
|
||||||
|
}
|
||||||
|
|
||||||
|
// instance returns a process-specific share path within tmpdir
|
||||||
|
func (share *shareHost) instance() string {
|
||||||
|
if share.sharePath != "" {
|
||||||
|
return share.sharePath
|
||||||
|
}
|
||||||
|
share.sharePath = path.Join(share.sc.SharePath, share.seal.id.String())
|
||||||
|
share.seal.sys.Ephemeral(system.Process, share.sharePath, 0711)
|
||||||
|
return share.sharePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime returns a process-specific share path within XDG_RUNTIME_DIR
|
||||||
|
func (share *shareHost) runtime() string {
|
||||||
|
if share.runtimeSharePath != "" {
|
||||||
|
return share.runtimeSharePath
|
||||||
|
}
|
||||||
|
share.ensureRuntimeDir()
|
||||||
|
share.runtimeSharePath = path.Join(share.sc.RunDirPath, share.seal.id.String())
|
||||||
|
share.seal.sys.Ephemeral(system.Process, share.runtimeSharePath, 0700)
|
||||||
|
share.seal.sys.UpdatePerm(share.runtimeSharePath, acl.Execute)
|
||||||
|
return share.runtimeSharePath
|
||||||
|
}
|
||||||
|
|
||||||
// fsuUser stores post-fsu credentials and metadata
|
// fsuUser stores post-fsu credentials and metadata
|
||||||
type fsuUser struct {
|
type fsuUser struct {
|
||||||
// application id
|
// application id
|
||||||
@ -109,11 +158,6 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
seal.ctx = ctx
|
seal.ctx = ctx
|
||||||
|
|
||||||
shellPath := "/bin/sh"
|
|
||||||
if s, ok := sys.LookupEnv(shell); ok && path.IsAbs(s) {
|
|
||||||
shellPath = s
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// encode initial configuration for state tracking
|
// encode initial configuration for state tracking
|
||||||
ct := new(bytes.Buffer)
|
ct := new(bytes.Buffer)
|
||||||
@ -125,20 +169,16 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allowed aid range 0 to 9999, this is checked again in fsu
|
// allowed aid range 0 to 9999, this is checked again in fsu
|
||||||
if config.Confinement.AppID < 0 || config.Confinement.AppID > 9999 {
|
if config.Identity < 0 || config.Identity > 9999 {
|
||||||
return fmsg.WrapError(ErrUser,
|
return fmsg.WrapError(ErrUser,
|
||||||
fmt.Sprintf("aid %d out of range", config.Confinement.AppID))
|
fmt.Sprintf("identity %d out of range", config.Identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Resolve post-fsu user state
|
|
||||||
*/
|
|
||||||
|
|
||||||
seal.user = fsuUser{
|
seal.user = fsuUser{
|
||||||
aid: newInt(config.Confinement.AppID),
|
aid: newInt(config.Identity),
|
||||||
data: config.Confinement.Outer,
|
data: config.Data,
|
||||||
home: config.Confinement.Inner,
|
home: config.Dir,
|
||||||
username: config.Confinement.Username,
|
username: config.Username,
|
||||||
}
|
}
|
||||||
if seal.user.username == "" {
|
if seal.user.username == "" {
|
||||||
seal.user.username = "chronos"
|
seal.user.username = "chronos"
|
||||||
@ -159,8 +199,8 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
} else {
|
} else {
|
||||||
seal.user.uid = newInt(u)
|
seal.user.uid = newInt(u)
|
||||||
}
|
}
|
||||||
seal.user.supp = make([]string, len(config.Confinement.Groups))
|
seal.user.supp = make([]string, len(config.Groups))
|
||||||
for i, name := range config.Confinement.Groups {
|
for i, name := range config.Groups {
|
||||||
if g, err := sys.LookupGroup(name); err != nil {
|
if g, err := sys.LookupGroup(name); err != nil {
|
||||||
return fmsg.WrapError(err,
|
return fmsg.WrapError(err,
|
||||||
fmt.Sprintf("unknown group %q", name))
|
fmt.Sprintf("unknown group %q", name))
|
||||||
@ -169,13 +209,18 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// this also falls back to host path if encountering an invalid path
|
||||||
Resolve initial container state
|
if !path.IsAbs(config.Shell) {
|
||||||
*/
|
config.Shell = "/bin/sh"
|
||||||
|
if s, ok := sys.LookupEnv(shell); ok && path.IsAbs(s) {
|
||||||
|
config.Shell = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// do not use the value of shell before this point
|
||||||
|
|
||||||
// permissive defaults
|
// permissive defaults
|
||||||
if config.Confinement.Sandbox == nil {
|
if config.Container == nil {
|
||||||
fmsg.Verbose("sandbox configuration not supplied, PROCEED WITH CAUTION")
|
fmsg.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
|
||||||
|
|
||||||
// fsu clears the environment so resolve paths early
|
// fsu clears the environment so resolve paths early
|
||||||
if !path.IsAbs(config.Path) {
|
if !path.IsAbs(config.Path) {
|
||||||
@ -186,11 +231,11 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
config.Path = p
|
config.Path = p
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
config.Path = shellPath
|
config.Path = config.Shell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := &fst.SandboxConfig{
|
conf := &fst.ContainerConfig{
|
||||||
Userns: true,
|
Userns: true,
|
||||||
Net: true,
|
Net: true,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
@ -223,20 +268,20 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
conf.Cover = append(conf.Cover, nscd)
|
conf.Cover = append(conf.Cover, nscd)
|
||||||
}
|
}
|
||||||
// bind GPU stuff
|
// bind GPU stuff
|
||||||
if config.Confinement.Enablements&(system.EX11|system.EWayland) != 0 {
|
if config.Enablements&(system.EX11|system.EWayland) != 0 {
|
||||||
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/dri", Device: true})
|
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/dri", Device: true})
|
||||||
}
|
}
|
||||||
// opportunistically bind kvm
|
// opportunistically bind kvm
|
||||||
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true})
|
conf.Filesystem = append(conf.Filesystem, &fst.FilesystemConfig{Src: "/dev/kvm", Device: true})
|
||||||
|
|
||||||
config.Confinement.Sandbox = conf
|
config.Container = conf
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapuid, mapgid *stringPair[int]
|
var mapuid, mapgid *stringPair[int]
|
||||||
{
|
{
|
||||||
var uid, gid int
|
var uid, gid int
|
||||||
var err error
|
var err error
|
||||||
seal.container, seal.env, err = config.Confinement.Sandbox.ToContainer(sys, &uid, &gid)
|
seal.container, seal.env, err = common.NewContainer(config.Container, sys, &uid, &gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return fmsg.WrapErrorSuffix(err,
|
||||||
"cannot initialise container configuration:")
|
"cannot initialise container configuration:")
|
||||||
@ -256,39 +301,21 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
if seal.env == nil {
|
if seal.env == nil {
|
||||||
seal.env = make(map[string]string, 1<<6)
|
seal.env = make(map[string]string, 1<<6)
|
||||||
}
|
}
|
||||||
seal.env[shell] = shellPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if !config.Container.AutoEtc {
|
||||||
Initialise externals
|
if config.Container.Etc != "" {
|
||||||
*/
|
seal.container.Bind(config.Container.Etc, "/etc", 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
etcPath := config.Container.Etc
|
||||||
|
if etcPath == "" {
|
||||||
|
etcPath = "/etc"
|
||||||
|
}
|
||||||
|
seal.container.Etc(etcPath, seal.id.String())
|
||||||
|
}
|
||||||
|
|
||||||
sc := sys.Paths()
|
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
|
||||||
seal.runDirPath = sc.RunDirPath
|
|
||||||
seal.sys = system.New(seal.user.uid.unwrap())
|
|
||||||
|
|
||||||
/*
|
|
||||||
Work directories
|
|
||||||
*/
|
|
||||||
|
|
||||||
// base fortify share path
|
|
||||||
seal.sys.Ensure(sc.SharePath, 0711)
|
|
||||||
|
|
||||||
// outer paths used by the main process
|
|
||||||
seal.sys.Ensure(sc.RunDirPath, 0700)
|
|
||||||
seal.sys.UpdatePermType(system.User, sc.RunDirPath, acl.Execute)
|
|
||||||
seal.sys.Ensure(sc.RuntimePath, 0700) // ensure this dir in case XDG_RUNTIME_DIR is unset
|
|
||||||
seal.sys.UpdatePermType(system.User, sc.RuntimePath, acl.Execute)
|
|
||||||
|
|
||||||
// outer process-specific share directory
|
|
||||||
sharePath := path.Join(sc.SharePath, seal.id.String())
|
|
||||||
seal.sys.Ephemeral(system.Process, sharePath, 0711)
|
|
||||||
// similar to share but within XDG_RUNTIME_DIR
|
|
||||||
sharePathLocal := path.Join(sc.RunDirPath, seal.id.String())
|
|
||||||
seal.sys.Ephemeral(system.Process, sharePathLocal, 0700)
|
|
||||||
seal.sys.UpdatePerm(sharePathLocal, acl.Execute)
|
|
||||||
|
|
||||||
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as post-fsu user
|
|
||||||
innerRuntimeDir := path.Join("/run/user", mapuid.String())
|
innerRuntimeDir := path.Join("/run/user", mapuid.String())
|
||||||
seal.container.Tmpfs("/run/user", 1<<12, 0755)
|
seal.container.Tmpfs("/run/user", 1<<12, 0755)
|
||||||
seal.container.Tmpfs(innerRuntimeDir, 1<<23, 0700)
|
seal.container.Tmpfs(innerRuntimeDir, 1<<23, 0700)
|
||||||
@ -296,21 +323,23 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
seal.env[xdgSessionClass] = "user"
|
seal.env[xdgSessionClass] = "user"
|
||||||
seal.env[xdgSessionType] = "tty"
|
seal.env[xdgSessionType] = "tty"
|
||||||
|
|
||||||
// outer path for inner /tmp
|
share := &shareHost{seal: seal, sc: sys.Paths()}
|
||||||
|
seal.runDirPath = share.sc.RunDirPath
|
||||||
|
seal.sys = system.New(seal.user.uid.unwrap())
|
||||||
|
|
||||||
{
|
{
|
||||||
tmpdir := path.Join(sc.SharePath, "tmpdir")
|
seal.sys.Ensure(share.sc.SharePath, 0711)
|
||||||
|
tmpdir := path.Join(share.sc.SharePath, "tmpdir")
|
||||||
seal.sys.Ensure(tmpdir, 0700)
|
seal.sys.Ensure(tmpdir, 0700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute)
|
seal.sys.UpdatePermType(system.User, tmpdir, acl.Execute)
|
||||||
tmpdirInst := path.Join(tmpdir, seal.user.aid.String())
|
tmpdirInst := path.Join(tmpdir, seal.user.aid.String())
|
||||||
seal.sys.Ensure(tmpdirInst, 01700)
|
seal.sys.Ensure(tmpdirInst, 01700)
|
||||||
seal.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute)
|
seal.sys.UpdatePermType(system.User, tmpdirInst, acl.Read, acl.Write, acl.Execute)
|
||||||
|
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
|
||||||
seal.container.Bind(tmpdirInst, "/tmp", sandbox.BindWritable)
|
seal.container.Bind(tmpdirInst, "/tmp", sandbox.BindWritable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
{
|
||||||
Passwd database
|
|
||||||
*/
|
|
||||||
|
|
||||||
homeDir := "/var/empty"
|
homeDir := "/var/empty"
|
||||||
if seal.user.home != "" {
|
if seal.user.home != "" {
|
||||||
homeDir = seal.user.home
|
homeDir = seal.user.home
|
||||||
@ -323,29 +352,27 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
seal.container.Dir = homeDir
|
seal.container.Dir = homeDir
|
||||||
seal.env["HOME"] = homeDir
|
seal.env["HOME"] = homeDir
|
||||||
seal.env["USER"] = username
|
seal.env["USER"] = username
|
||||||
|
seal.env[shell] = config.Shell
|
||||||
|
|
||||||
seal.container.Place("/etc/passwd",
|
seal.container.Place("/etc/passwd",
|
||||||
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+shellPath+"\n"))
|
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Fortify:"+homeDir+":"+config.Shell+"\n"))
|
||||||
seal.container.Place("/etc/group",
|
seal.container.Place("/etc/group",
|
||||||
[]byte("fortify:x:"+mapgid.String()+":\n"))
|
[]byte("fortify:x:"+mapgid.String()+":\n"))
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
// pass TERM for proper terminal I/O in initial process
|
||||||
Display servers
|
|
||||||
*/
|
|
||||||
|
|
||||||
// pass $TERM for proper terminal I/O in shell
|
|
||||||
if t, ok := sys.LookupEnv(term); ok {
|
if t, ok := sys.LookupEnv(term); ok {
|
||||||
seal.env[term] = t
|
seal.env[term] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Confinement.Enablements&system.EWayland != 0 {
|
if config.Enablements&system.EWayland != 0 {
|
||||||
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
|
||||||
var socketPath string
|
var socketPath string
|
||||||
if name, ok := sys.LookupEnv(wl.WaylandDisplay); !ok {
|
if name, ok := sys.LookupEnv(wl.WaylandDisplay); !ok {
|
||||||
fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
fmsg.Verbose(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
||||||
socketPath = path.Join(sc.RuntimePath, wl.FallbackName)
|
socketPath = path.Join(share.sc.RuntimePath, wl.FallbackName)
|
||||||
} else if !path.IsAbs(name) {
|
} else if !path.IsAbs(name) {
|
||||||
socketPath = path.Join(sc.RuntimePath, name)
|
socketPath = path.Join(share.sc.RuntimePath, name)
|
||||||
} else {
|
} else {
|
||||||
socketPath = name
|
socketPath = name
|
||||||
}
|
}
|
||||||
@ -353,25 +380,25 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
innerPath := path.Join(innerRuntimeDir, wl.FallbackName)
|
innerPath := path.Join(innerRuntimeDir, wl.FallbackName)
|
||||||
seal.env[wl.WaylandDisplay] = wl.FallbackName
|
seal.env[wl.WaylandDisplay] = wl.FallbackName
|
||||||
|
|
||||||
if !config.Confinement.Sandbox.DirectWayland { // set up security-context-v1
|
if !config.DirectWayland { // set up security-context-v1
|
||||||
socketDir := path.Join(sc.SharePath, "wayland")
|
|
||||||
outerPath := path.Join(socketDir, seal.id.String())
|
|
||||||
seal.sys.Ensure(socketDir, 0711)
|
|
||||||
appID := config.ID
|
appID := config.ID
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
// use instance ID in case app id is not set
|
// use instance ID in case app id is not set
|
||||||
appID = "uk.gensokyo.fortify." + seal.id.String()
|
appID = "uk.gensokyo.fortify." + seal.id.String()
|
||||||
}
|
}
|
||||||
|
// downstream socket paths
|
||||||
|
outerPath := path.Join(share.instance(), "wayland")
|
||||||
seal.sys.Wayland(&seal.sync, outerPath, socketPath, appID, seal.id.String())
|
seal.sys.Wayland(&seal.sync, outerPath, socketPath, appID, seal.id.String())
|
||||||
seal.container.Bind(outerPath, innerPath, 0)
|
seal.container.Bind(outerPath, innerPath, 0)
|
||||||
} else { // bind mount wayland socket (insecure)
|
} else { // bind mount wayland socket (insecure)
|
||||||
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
fmsg.Verbose("direct wayland access, PROCEED WITH CAUTION")
|
||||||
|
share.ensureRuntimeDir()
|
||||||
seal.container.Bind(socketPath, innerPath, 0)
|
seal.container.Bind(socketPath, innerPath, 0)
|
||||||
seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
|
seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Confinement.Enablements&system.EX11 != 0 {
|
if config.Enablements&system.EX11 != 0 {
|
||||||
if d, ok := sys.LookupEnv(display); !ok {
|
if d, ok := sys.LookupEnv(display); !ok {
|
||||||
return fmsg.WrapError(ErrXDisplay,
|
return fmsg.WrapError(ErrXDisplay,
|
||||||
"DISPLAY is not set")
|
"DISPLAY is not set")
|
||||||
@ -382,13 +409,9 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if config.Enablements&system.EPulse != 0 {
|
||||||
PulseAudio server and authentication
|
|
||||||
*/
|
|
||||||
|
|
||||||
if config.Confinement.Enablements&system.EPulse != 0 {
|
|
||||||
// PulseAudio runtime directory (usually `/run/user/%d/pulse`)
|
// PulseAudio runtime directory (usually `/run/user/%d/pulse`)
|
||||||
pulseRuntimeDir := path.Join(sc.RuntimePath, "pulse")
|
pulseRuntimeDir := path.Join(share.sc.RuntimePath, "pulse")
|
||||||
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
// PulseAudio socket (usually `/run/user/%d/pulse/native`)
|
||||||
pulseSocket := path.Join(pulseRuntimeDir, "native")
|
pulseSocket := path.Join(pulseRuntimeDir, "native")
|
||||||
|
|
||||||
@ -416,7 +439,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hard link pulse socket into target-executable share
|
// hard link pulse socket into target-executable share
|
||||||
innerPulseRuntimeDir := path.Join(sharePathLocal, "pulse")
|
innerPulseRuntimeDir := path.Join(share.runtime(), "pulse")
|
||||||
innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native")
|
innerPulseSocket := path.Join(innerRuntimeDir, "pulse", "native")
|
||||||
seal.sys.Link(pulseSocket, innerPulseRuntimeDir)
|
seal.sys.Link(pulseSocket, innerPulseRuntimeDir)
|
||||||
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
|
||||||
@ -435,22 +458,19 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if config.Enablements&system.EDBus != 0 {
|
||||||
D-Bus proxy
|
|
||||||
*/
|
|
||||||
|
|
||||||
if config.Confinement.Enablements&system.EDBus != 0 {
|
|
||||||
// ensure dbus session bus defaults
|
// ensure dbus session bus defaults
|
||||||
if config.Confinement.SessionBus == nil {
|
if config.SessionBus == nil {
|
||||||
config.Confinement.SessionBus = dbus.NewConfig(config.ID, true, true)
|
config.SessionBus = dbus.NewConfig(config.ID, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// downstream socket paths
|
// downstream socket paths
|
||||||
|
sharePath := share.instance()
|
||||||
sessionPath, systemPath := path.Join(sharePath, "bus"), path.Join(sharePath, "system_bus_socket")
|
sessionPath, systemPath := path.Join(sharePath, "bus"), path.Join(sharePath, "system_bus_socket")
|
||||||
|
|
||||||
// configure dbus proxy
|
// configure dbus proxy
|
||||||
if f, err := seal.sys.ProxyDBus(
|
if f, err := seal.sys.ProxyDBus(
|
||||||
config.Confinement.SessionBus, config.Confinement.SystemBus,
|
config.SessionBus, config.SystemBus,
|
||||||
sessionPath, systemPath,
|
sessionPath, systemPath,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -463,7 +483,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner
|
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner
|
||||||
seal.container.Bind(sessionPath, sessionInner, 0)
|
seal.container.Bind(sessionPath, sessionInner, 0)
|
||||||
seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
|
seal.sys.UpdatePerm(sessionPath, acl.Read, acl.Write)
|
||||||
if config.Confinement.SystemBus != nil {
|
if config.SystemBus != nil {
|
||||||
systemInner := "/run/dbus/system_bus_socket"
|
systemInner := "/run/dbus/system_bus_socket"
|
||||||
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner
|
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner
|
||||||
seal.container.Bind(systemPath, systemInner, 0)
|
seal.container.Bind(systemPath, systemInner, 0)
|
||||||
@ -471,16 +491,12 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
for _, dest := range config.Container.Cover {
|
||||||
Miscellaneous
|
|
||||||
*/
|
|
||||||
|
|
||||||
for _, dest := range config.Confinement.Sandbox.Cover {
|
|
||||||
seal.container.Tmpfs(dest, 1<<13, 0755)
|
seal.container.Tmpfs(dest, 1<<13, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
// append ExtraPerms last
|
// append ExtraPerms last
|
||||||
for _, p := range config.Confinement.ExtraPerms {
|
for _, p := range config.ExtraPerms {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -513,8 +529,10 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *fst.Co
|
|||||||
}
|
}
|
||||||
slices.Sort(seal.container.Env)
|
slices.Sort(seal.container.Env)
|
||||||
|
|
||||||
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s",
|
if fmsg.Load() {
|
||||||
seal.user.uid, seal.user.username, config.Confinement.Groups, seal.container.Args)
|
fmsg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
|
||||||
|
seal.user.uid, seal.user.username, config.Groups, seal.container.Args, len(*seal.container.Ops))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -1,26 +1,78 @@
|
|||||||
package app
|
package setuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/gob"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
|
"git.gensokyo.uk/security/fortify/sandbox/seccomp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
static pid_t f_shim_param_ppid = -1;
|
||||||
|
|
||||||
|
// this cannot unblock fmsg since Go code is not async-signal-safe
|
||||||
|
static void f_shim_sigaction(int sig, siginfo_t *si, void *ucontext) {
|
||||||
|
if (sig != SIGCONT || si == NULL) {
|
||||||
|
// unreachable
|
||||||
|
fprintf(stderr, "sigaction: sa_sigaction got invalid siginfo\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor requests shim exit
|
||||||
|
if (si->si_pid == f_shim_param_ppid)
|
||||||
|
exit(254);
|
||||||
|
|
||||||
|
fprintf(stderr, "sigaction: got SIGCONT from process %d\n", si->si_pid);
|
||||||
|
|
||||||
|
// shim orphaned before monitor delivers a signal
|
||||||
|
if (getppid() != f_shim_param_ppid)
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void f_shim_setup_cont_signal(pid_t ppid) {
|
||||||
|
struct sigaction new_action = {0}, old_action = {0};
|
||||||
|
if (sigaction(SIGCONT, NULL, &old_action) != 0)
|
||||||
|
return;
|
||||||
|
if (old_action.sa_handler != SIG_DFL) {
|
||||||
|
errno = ENOTRECOVERABLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_action.sa_sigaction = f_shim_sigaction;
|
||||||
|
if (sigemptyset(&new_action.sa_mask) != 0)
|
||||||
|
return;
|
||||||
|
new_action.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||||
|
|
||||||
|
if (sigaction(SIGCONT, &new_action, NULL) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
f_shim_param_ppid = ppid;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
const shimEnv = "FORTIFY_SHIM"
|
const shimEnv = "FORTIFY_SHIM"
|
||||||
|
|
||||||
type shimParams struct {
|
type shimParams struct {
|
||||||
|
// monitor pid, checked against ppid in signal handler
|
||||||
|
Monitor int
|
||||||
|
|
||||||
// finalised container params
|
// finalised container params
|
||||||
Container *sandbox.Params
|
Container *sandbox.Params
|
||||||
// path to outer home directory
|
// path to outer home directory
|
||||||
@ -54,6 +106,16 @@ func ShimMain() {
|
|||||||
} else {
|
} else {
|
||||||
internal.InstallFmsg(params.Verbose)
|
internal.InstallFmsg(params.Verbose)
|
||||||
closeSetup = f
|
closeSetup = f
|
||||||
|
|
||||||
|
// the Go runtime does not expose siginfo_t so SIGCONT is handled in C to check si_pid
|
||||||
|
if _, err = C.f_shim_setup_cont_signal(C.pid_t(params.Monitor)); err != nil {
|
||||||
|
log.Fatalf("cannot install SIGCONT handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdeath_signal delivery is checked as if the dying process called kill(2), see kernel/exit.c
|
||||||
|
if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGCONT), 0); errno != 0 {
|
||||||
|
log.Fatalf("cannot set parent-death signal: %v", errno)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Container == nil || params.Container.Ops == nil {
|
if params.Container == nil || params.Container.Ops == nil {
|
||||||
@ -100,6 +162,11 @@ func ShimMain() {
|
|||||||
if err := container.Serve(); err != nil {
|
if err := container.Serve(); err != nil {
|
||||||
fmsg.PrintBaseError(err, "cannot configure container:")
|
fmsg.PrintBaseError(err, "cannot configure container:")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := seccomp.Load(seccomp.PresetCommon); err != nil {
|
||||||
|
log.Fatalf("cannot load syscall filter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := container.Wait(); err != nil {
|
if err := container.Wait(); err != nil {
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if !errors.As(err, &exitError) {
|
if !errors.As(err, &exitError) {
|
||||||
@ -112,101 +179,3 @@ func ShimMain() {
|
|||||||
os.Exit(exitError.ExitCode())
|
os.Exit(exitError.ExitCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type shimProcess struct {
|
|
||||||
// user switcher process
|
|
||||||
cmd *exec.Cmd
|
|
||||||
// fallback exit notifier with error returned killing the process
|
|
||||||
killFallback chan error
|
|
||||||
// monitor to shim encoder
|
|
||||||
encoder *gob.Encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *shimProcess) Unwrap() *exec.Cmd { return s.cmd }
|
|
||||||
func (s *shimProcess) Fallback() chan error { return s.killFallback }
|
|
||||||
|
|
||||||
func (s *shimProcess) String() string {
|
|
||||||
if s.cmd == nil {
|
|
||||||
return "(unused shim manager)"
|
|
||||||
}
|
|
||||||
return s.cmd.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *shimProcess) Start(
|
|
||||||
aid string,
|
|
||||||
supp []string,
|
|
||||||
) (*time.Time, error) {
|
|
||||||
// prepare user switcher invocation
|
|
||||||
fsuPath := internal.MustFsuPath()
|
|
||||||
s.cmd = exec.Command(fsuPath)
|
|
||||||
|
|
||||||
// pass shim setup pipe
|
|
||||||
if fd, e, err := sandbox.Setup(&s.cmd.ExtraFiles); err != nil {
|
|
||||||
return nil, fmsg.WrapErrorSuffix(err,
|
|
||||||
"cannot create shim setup pipe:")
|
|
||||||
} else {
|
|
||||||
s.encoder = e
|
|
||||||
s.cmd.Env = []string{
|
|
||||||
shimEnv + "=" + strconv.Itoa(fd),
|
|
||||||
"FORTIFY_APP_ID=" + aid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format fsu supplementary groups
|
|
||||||
if len(supp) > 0 {
|
|
||||||
fmsg.Verbosef("attaching supplementary group ids %s", supp)
|
|
||||||
s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(supp, " "))
|
|
||||||
}
|
|
||||||
s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
|
||||||
s.cmd.Dir = "/"
|
|
||||||
|
|
||||||
fmsg.Verbose("starting shim via fsu:", s.cmd)
|
|
||||||
// withhold messages to stderr
|
|
||||||
fmsg.Suspend()
|
|
||||||
if err := s.cmd.Start(); err != nil {
|
|
||||||
return nil, fmsg.WrapErrorSuffix(err,
|
|
||||||
"cannot start fsu:")
|
|
||||||
}
|
|
||||||
startTime := time.Now().UTC()
|
|
||||||
|
|
||||||
return &startTime, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *shimProcess) Serve(ctx context.Context, params *shimParams) error {
|
|
||||||
// kill shim if something goes wrong and an error is returned
|
|
||||||
s.killFallback = make(chan error, 1)
|
|
||||||
killShim := func() {
|
|
||||||
if err := s.cmd.Process.Signal(os.Interrupt); err != nil {
|
|
||||||
s.killFallback <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() { killShim() }()
|
|
||||||
|
|
||||||
encodeErr := make(chan error)
|
|
||||||
go func() { encodeErr <- s.encoder.Encode(params) }()
|
|
||||||
|
|
||||||
select {
|
|
||||||
// encode return indicates setup completion
|
|
||||||
case err := <-encodeErr:
|
|
||||||
if err != nil {
|
|
||||||
return fmsg.WrapErrorSuffix(err,
|
|
||||||
"cannot transmit shim config:")
|
|
||||||
}
|
|
||||||
killShim = func() {}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
// setup canceled before payload was accepted
|
|
||||||
case <-ctx.Done():
|
|
||||||
err := ctx.Err()
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
return fmsg.WrapError(syscall.ECANCELED,
|
|
||||||
"shim setup canceled")
|
|
||||||
}
|
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
return fmsg.WrapError(syscall.ETIMEDOUT,
|
|
||||||
"deadline exceeded waiting for shim")
|
|
||||||
}
|
|
||||||
// unreachable
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
package app
|
package setuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
. "git.gensokyo.uk/security/fortify/internal/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} }
|
||||||
func newID(id *fst.ID) *stringPair[fst.ID] { return &stringPair[fst.ID]{*id, id.String()} }
|
func newID(id *ID) *stringPair[ID] { return &stringPair[ID]{*id, id.String()} }
|
||||||
|
|
||||||
// stringPair stores a value and its string representation.
|
// stringPair stores a value and its string representation.
|
||||||
type stringPair[T comparable] struct {
|
type stringPair[T comparable] struct {
|
@ -1,180 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
const shimSetupTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
func (seal *outcome) Run(rs *fst.RunState) error {
|
|
||||||
if !seal.f.CompareAndSwap(false, true) {
|
|
||||||
// run does much more than just starting a process; calling it twice, even if the first call fails, will result
|
|
||||||
// in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
|
|
||||||
// other Run a chance to return
|
|
||||||
panic("attempted to run twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs == nil {
|
|
||||||
panic("invalid state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// read comp values early to allow for early failure
|
|
||||||
fmsg.Verbosef("version %s", internal.Version())
|
|
||||||
fmsg.Verbosef("setuid helper at %s", internal.MustFsuPath())
|
|
||||||
|
|
||||||
/*
|
|
||||||
prepare/revert os state
|
|
||||||
*/
|
|
||||||
|
|
||||||
if err := seal.sys.Commit(seal.ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
store := state.NewMulti(seal.runDirPath)
|
|
||||||
deferredStoreFunc := func(c state.Cursor) error { return nil }
|
|
||||||
defer func() {
|
|
||||||
var revertErr error
|
|
||||||
storeErr := new(StateStoreError)
|
|
||||||
storeErr.Inner, storeErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
|
||||||
revertErr = func() error {
|
|
||||||
storeErr.InnerErr = deferredStoreFunc(c)
|
|
||||||
|
|
||||||
/*
|
|
||||||
revert app setup transaction
|
|
||||||
*/
|
|
||||||
|
|
||||||
var rt system.Enablement
|
|
||||||
ec := system.Process
|
|
||||||
if states, err := c.Load(); err != nil {
|
|
||||||
// revert per-process state here to limit damage
|
|
||||||
storeErr.OpErr = err
|
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
|
||||||
} else {
|
|
||||||
if l := len(states); l == 0 {
|
|
||||||
fmsg.Verbose("no other launchers active, will clean up globals")
|
|
||||||
ec |= system.User
|
|
||||||
} else {
|
|
||||||
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// accumulate enablements of remaining launchers
|
|
||||||
for i, s := range states {
|
|
||||||
if s.Config != nil {
|
|
||||||
rt |= s.Config.Confinement.Enablements
|
|
||||||
} else {
|
|
||||||
log.Printf("state entry %d does not contain config", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
|
|
||||||
if fmsg.Load() {
|
|
||||||
if ec > 0 {
|
|
||||||
fmsg.Verbose("reverting operations type", system.TypeString(ec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return seal.sys.Revert((*system.Criteria)(&ec))
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
storeErr.save([]error{revertErr, store.Close()})
|
|
||||||
rs.RevertErr = storeErr.equiv("error returned during cleanup:")
|
|
||||||
}()
|
|
||||||
|
|
||||||
/*
|
|
||||||
shim process lifecycle
|
|
||||||
*/
|
|
||||||
|
|
||||||
waitErr := make(chan error, 1)
|
|
||||||
cmd := new(shimProcess)
|
|
||||||
if startTime, err := cmd.Start(
|
|
||||||
seal.user.aid.String(),
|
|
||||||
seal.user.supp,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
// whether/when the fsu process was created
|
|
||||||
rs.Time = startTime
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(seal.ctx, shimSetupTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
waitErr <- cmd.Unwrap().Wait()
|
|
||||||
// cancel shim setup in case shim died before receiving payload
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := cmd.Serve(ctx, &shimParams{
|
|
||||||
Container: seal.container,
|
|
||||||
Home: seal.user.data,
|
|
||||||
|
|
||||||
Verbose: fmsg.Load(),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// shim accepted setup payload, create process state
|
|
||||||
sd := state.State{
|
|
||||||
ID: seal.id.unwrap(),
|
|
||||||
PID: cmd.Unwrap().Process.Pid,
|
|
||||||
Time: *rs.Time,
|
|
||||||
}
|
|
||||||
var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr
|
|
||||||
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
|
||||||
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
|
||||||
})
|
|
||||||
// destroy defunct state entry
|
|
||||||
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-waitErr: // block until fsu/shim returns
|
|
||||||
if err != nil {
|
|
||||||
var exitError *exec.ExitError
|
|
||||||
if !errors.As(err, &exitError) {
|
|
||||||
// should be unreachable
|
|
||||||
rs.WaitErr = err
|
|
||||||
}
|
|
||||||
|
|
||||||
// store non-zero return code
|
|
||||||
rs.ExitCode = exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
rs.ExitCode = cmd.Unwrap().ProcessState.ExitCode()
|
|
||||||
}
|
|
||||||
if fmsg.Load() {
|
|
||||||
fmsg.Verbosef("process %d exited with exit code %d", cmd.Unwrap().Process.Pid, rs.ExitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is reached when a fault makes an already running shim impossible to continue execution
|
|
||||||
// however a kill signal could not be delivered (should actually always happen like that since fsu)
|
|
||||||
// the effects of this is similar to the alternative exit path and ensures shim death
|
|
||||||
case err := <-cmd.Fallback():
|
|
||||||
rs.ExitCode = 255
|
|
||||||
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
|
||||||
|
|
||||||
// alternative exit path relying on shim behaviour on monitor process exit
|
|
||||||
case <-seal.ctx.Done():
|
|
||||||
fmsg.Verbose("alternative exit path selected")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmsg.Resume()
|
|
||||||
if seal.sync != nil {
|
|
||||||
if err := seal.sync.Close(); err != nil {
|
|
||||||
log.Printf("cannot close wayland security context: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if seal.dbusMsg != nil {
|
|
||||||
seal.dbusMsg()
|
|
||||||
}
|
|
||||||
|
|
||||||
return earlyStoreErr.equiv("cannot save process state:")
|
|
||||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ type multiBackend struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *multiBackend) filename(id *fst.ID) string {
|
func (b *multiBackend) filename(id *app.ID) string {
|
||||||
return path.Join(b.path, id.String())
|
return path.Join(b.path, id.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,8 +190,8 @@ func (b *multiBackend) load(decode bool) (Entries, error) {
|
|||||||
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
|
return nil, fmt.Errorf("unexpected directory %q in store", e.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
id := new(fst.ID)
|
id := new(app.ID)
|
||||||
if err := fst.ParseAppID(id, e.Name()); err != nil {
|
if err := app.ParseAppID(id, e.Name()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +336,7 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *multiBackend) Destroy(id fst.ID) error {
|
func (b *multiBackend) Destroy(id app.ID) error {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
|
@ -6,11 +6,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNoConfig = errors.New("state does not contain config")
|
var ErrNoConfig = errors.New("state does not contain config")
|
||||||
|
|
||||||
type Entries map[fst.ID]*State
|
type Entries map[app.ID]*State
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
// Do calls f exactly once and ensures store exclusivity until f returns.
|
// Do calls f exactly once and ensures store exclusivity until f returns.
|
||||||
@ -29,7 +30,7 @@ type Store interface {
|
|||||||
// Cursor provides access to the store
|
// Cursor provides access to the store
|
||||||
type Cursor interface {
|
type Cursor interface {
|
||||||
Save(state *State, configWriter io.WriterTo) error
|
Save(state *State, configWriter io.WriterTo) error
|
||||||
Destroy(id fst.ID) error
|
Destroy(id app.ID) error
|
||||||
Load() (Entries, error)
|
Load() (Entries, error)
|
||||||
Len() (int, error)
|
Len() (int, error)
|
||||||
}
|
}
|
||||||
@ -37,7 +38,7 @@ type Cursor interface {
|
|||||||
// State is a fortify process's state
|
// State is a fortify process's state
|
||||||
type State struct {
|
type State struct {
|
||||||
// fortify instance id
|
// fortify instance id
|
||||||
ID fst.ID `json:"instance"`
|
ID app.ID `json:"instance"`
|
||||||
// child process PID value
|
// child process PID value
|
||||||
PID int `json:"pid"`
|
PID int `json:"pid"`
|
||||||
// sealed app configuration
|
// sealed app configuration
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ func testStore(t *testing.T, s state.Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeState(t *testing.T, s *state.State, ct io.Writer) {
|
func makeState(t *testing.T, s *state.State, ct io.Writer) {
|
||||||
if err := fst.NewAppID(&s.ID); err != nil {
|
if err := app.NewAppID(&s.ID); err != nil {
|
||||||
t.Fatalf("cannot create dummy state: %v", err)
|
t.Fatalf("cannot create dummy state: %v", err)
|
||||||
}
|
}
|
||||||
if err := gob.NewEncoder(ct).Encode(fst.Template()); err != nil {
|
if err := gob.NewEncoder(ct).Encode(fst.Template()); err != nil {
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ type State interface {
|
|||||||
Printf(format string, v ...any)
|
Printf(format string, v ...any)
|
||||||
|
|
||||||
// Paths returns a populated [Paths] struct.
|
// Paths returns a populated [Paths] struct.
|
||||||
Paths() fst.Paths
|
Paths() app.Paths
|
||||||
// Uid invokes fsu and returns target uid.
|
// Uid invokes fsu and returns target uid.
|
||||||
// Any errors returned by Uid is already wrapped [fmsg.BaseError].
|
// Any errors returned by Uid is already wrapped [fmsg.BaseError].
|
||||||
Uid(aid int) (int, error)
|
Uid(aid int) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyPaths is a generic implementation of [fst.Paths].
|
// CopyPaths is a generic implementation of [fst.Paths].
|
||||||
func CopyPaths(os State, v *fst.Paths) {
|
func CopyPaths(os State, v *app.Paths) {
|
||||||
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Getuid()))
|
v.SharePath = path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Getuid()))
|
||||||
|
|
||||||
fmsg.Verbosef("process share directory at %q", v.SharePath)
|
fmsg.Verbosef("process share directory at %q", v.SharePath)
|
||||||
|
@ -12,15 +12,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/sandbox"
|
"git.gensokyo.uk/security/fortify/sandbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Std implements System using the standard library.
|
// Std implements System using the standard library.
|
||||||
type Std struct {
|
type Std struct {
|
||||||
paths fst.Paths
|
paths app.Paths
|
||||||
pathsOnce sync.Once
|
pathsOnce sync.Once
|
||||||
|
|
||||||
uidOnce sync.Once
|
uidOnce sync.Once
|
||||||
@ -48,7 +48,7 @@ func (s *Std) Printf(format string, v ...any) { fmsg.Verbosef(form
|
|||||||
|
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
func (s *Std) Paths() fst.Paths {
|
func (s *Std) Paths() app.Paths {
|
||||||
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) })
|
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths) })
|
||||||
return s.paths
|
return s.paths
|
||||||
}
|
}
|
||||||
|
45
main.go
45
main.go
@ -20,6 +20,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/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app/instance"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
@ -73,7 +74,7 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { app.ShimMain(); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess })
|
||||||
|
|
||||||
c.Command("app", "Launch app defined by the specified config file", func(args []string) error {
|
c.Command("app", "Launch app defined by the specified config file", func(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
@ -153,33 +154,33 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
userName = passwd.Username
|
userName = passwd.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Confinement.AppID = aid
|
config.Identity = aid
|
||||||
config.Confinement.Groups = groups
|
config.Groups = groups
|
||||||
config.Confinement.Outer = homeDir
|
config.Data = homeDir
|
||||||
config.Confinement.Username = userName
|
config.Username = userName
|
||||||
|
|
||||||
if wayland {
|
if wayland {
|
||||||
config.Confinement.Enablements |= system.EWayland
|
config.Enablements |= system.EWayland
|
||||||
}
|
}
|
||||||
if x11 {
|
if x11 {
|
||||||
config.Confinement.Enablements |= system.EX11
|
config.Enablements |= system.EX11
|
||||||
}
|
}
|
||||||
if dBus {
|
if dBus {
|
||||||
config.Confinement.Enablements |= system.EDBus
|
config.Enablements |= system.EDBus
|
||||||
}
|
}
|
||||||
if pulse {
|
if pulse {
|
||||||
config.Confinement.Enablements |= system.EPulse
|
config.Enablements |= system.EPulse
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse D-Bus config file from flags if applicable
|
// parse D-Bus config file from flags if applicable
|
||||||
if dBus {
|
if dBus {
|
||||||
if dbusConfigSession == "builtin" {
|
if dbusConfigSession == "builtin" {
|
||||||
config.Confinement.SessionBus = dbus.NewConfig(fid, true, mpris)
|
config.SessionBus = dbus.NewConfig(fid, true, mpris)
|
||||||
} else {
|
} else {
|
||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
if conf, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
|
||||||
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
log.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SessionBus = conf
|
config.SessionBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,14 +189,14 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
if conf, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
|
||||||
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
log.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
|
||||||
} else {
|
} else {
|
||||||
config.Confinement.SystemBus = conf
|
config.SystemBus = conf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override log from configuration
|
// override log from configuration
|
||||||
if dbusVerbose {
|
if dbusVerbose {
|
||||||
config.Confinement.SessionBus.Log = true
|
config.SessionBus.Log = true
|
||||||
config.Confinement.SystemBus.Log = true
|
config.SystemBus.Log = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,11 +240,11 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
|
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := args[0]
|
name := args[0]
|
||||||
config, instance := tryShort(name)
|
config, entry := tryShort(name)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = tryPath(name)
|
config = tryPath(name)
|
||||||
}
|
}
|
||||||
printShowInstance(os.Stdout, time.Now().UTC(), instance, config, showFlagShort, flagJSON)
|
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, showFlagShort, flagJSON)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Fatal("show requires 1 argument")
|
log.Fatal("show requires 1 argument")
|
||||||
@ -284,15 +285,15 @@ func runApp(config *fst.Config) {
|
|||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
a := app.MustNew(ctx, std)
|
a := instance.MustNew(instance.ISetuid, ctx, std)
|
||||||
|
|
||||||
rs := new(fst.RunState)
|
rs := new(app.RunState)
|
||||||
if sa, err := a.Seal(config); err != nil {
|
if sa, err := a.Seal(config); err != nil {
|
||||||
fmsg.PrintBaseError(err, "cannot seal app:")
|
fmsg.PrintBaseError(err, "cannot seal app:")
|
||||||
rs.ExitCode = 1
|
internal.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
// this updates ExitCode
|
internal.Exit(instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)))
|
||||||
app.PrintRunStateErr(rs, sa.Run(rs))
|
|
||||||
}
|
}
|
||||||
internal.Exit(rs.ExitCode)
|
|
||||||
|
*(*int)(nil) = 0 // not reached
|
||||||
}
|
}
|
||||||
|
23
nixos.nix
23
nixos.nix
@ -88,6 +88,7 @@ in
|
|||||||
|
|
||||||
conf = {
|
conf = {
|
||||||
inherit (app) id;
|
inherit (app) id;
|
||||||
|
|
||||||
path =
|
path =
|
||||||
if app.path == null then
|
if app.path == null then
|
||||||
pkgs.writeScript "${app.name}-start" ''
|
pkgs.writeScript "${app.name}-start" ''
|
||||||
@ -98,23 +99,28 @@ in
|
|||||||
app.path;
|
app.path;
|
||||||
args = if app.args == null then [ "${app.name}-start" ] else app.args;
|
args = if app.args == null then [ "${app.name}-start" ] else app.args;
|
||||||
|
|
||||||
confinement = {
|
inherit enablements;
|
||||||
app_id = aid;
|
|
||||||
inherit (app) groups;
|
inherit (dbusConfig) session_bus system_bus;
|
||||||
|
direct_wayland = app.insecureWayland;
|
||||||
|
|
||||||
username = getsubname fid aid;
|
username = getsubname fid aid;
|
||||||
home = getsubhome fid aid;
|
data = getsubhome fid aid;
|
||||||
sandbox = {
|
|
||||||
|
identity = aid;
|
||||||
|
inherit (app) groups;
|
||||||
|
|
||||||
|
container = {
|
||||||
inherit (app)
|
inherit (app)
|
||||||
devel
|
devel
|
||||||
userns
|
userns
|
||||||
net
|
net
|
||||||
dev
|
device
|
||||||
tty
|
tty
|
||||||
multiarch
|
multiarch
|
||||||
env
|
env
|
||||||
;
|
;
|
||||||
map_real_uid = app.mapRealUid;
|
map_real_uid = app.mapRealUid;
|
||||||
direct_wayland = app.insecureWayland;
|
|
||||||
|
|
||||||
filesystem =
|
filesystem =
|
||||||
let
|
let
|
||||||
@ -177,9 +183,6 @@ in
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit enablements;
|
|
||||||
inherit (dbusConfig) session_bus system_bus;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.writeShellScriptBin app.name ''
|
pkgs.writeShellScriptBin app.name ''
|
||||||
|
12
options.md
12
options.md
@ -35,7 +35,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation fortify-static-x86_64-unknown-linux-musl-0.3.2> `
|
` <derivation fortify-static-x86_64-unknown-linux-musl-0.4.0> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -222,11 +222,11 @@ null or anything
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## environment\.fortify\.apps\.\*\.dev
|
## environment\.fortify\.apps\.\*\.devel
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Whether to enable access to all devices\.
|
Whether to enable debugging-related kernel interfaces\.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -245,11 +245,11 @@ boolean
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## environment\.fortify\.apps\.\*\.devel
|
## environment\.fortify\.apps\.\*\.device
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Whether to enable debugging-related kernel interfaces\.
|
Whether to enable access to all devices\.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -644,7 +644,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation fortify-fsu-0.3.2> `
|
` <derivation fortify-fsu-0.4.0> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ in
|
|||||||
|
|
||||||
nix = mkEnableOption "nix daemon access";
|
nix = mkEnableOption "nix daemon access";
|
||||||
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
||||||
dev = mkEnableOption "access to all devices";
|
device = mkEnableOption "access to all devices";
|
||||||
insecureWayland = mkEnableOption "direct access to the Wayland socket";
|
insecureWayland = mkEnableOption "direct access to the Wayland socket";
|
||||||
|
|
||||||
gpu = mkOption {
|
gpu = mkOption {
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "0.3.2";
|
version = "0.4.0";
|
||||||
|
|
||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
|
6
parse.go
6
parse.go
@ -67,7 +67,7 @@ func tryFd(name string) io.ReadCloser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryShort(name string) (config *fst.Config, instance *state.State) {
|
func tryShort(name string) (config *fst.Config, entry *state.State) {
|
||||||
likePrefix := false
|
likePrefix := false
|
||||||
if len(name) <= 32 {
|
if len(name) <= 32 {
|
||||||
likePrefix = true
|
likePrefix = true
|
||||||
@ -96,8 +96,8 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
v := id.String()
|
v := id.String()
|
||||||
if strings.HasPrefix(v, name) {
|
if strings.HasPrefix(v, name) {
|
||||||
// match, use config from this state entry
|
// match, use config from this state entry
|
||||||
instance = entries[id]
|
entry = entries[id]
|
||||||
config = instance.Config
|
config = entry.Config
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
96
print.go
96
print.go
@ -56,7 +56,7 @@ func printShowInstance(
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
if config.Confinement.Sandbox == nil {
|
if config.Container == nil {
|
||||||
mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
|
mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,19 +69,21 @@ func printShowInstance(
|
|||||||
|
|
||||||
t.Printf("App\n")
|
t.Printf("App\n")
|
||||||
if config.ID != "" {
|
if config.ID != "" {
|
||||||
t.Printf(" ID:\t%d (%s)\n", config.Confinement.AppID, config.ID)
|
t.Printf(" ID:\t%d (%s)\n", config.Identity, config.ID)
|
||||||
} else {
|
} else {
|
||||||
t.Printf(" ID:\t%d\n", config.Confinement.AppID)
|
t.Printf(" ID:\t%d\n", config.Identity)
|
||||||
}
|
}
|
||||||
t.Printf(" Enablements:\t%s\n", config.Confinement.Enablements.String())
|
t.Printf(" Enablements:\t%s\n", config.Enablements.String())
|
||||||
if len(config.Confinement.Groups) > 0 {
|
if len(config.Groups) > 0 {
|
||||||
t.Printf(" Groups:\t%q\n", config.Confinement.Groups)
|
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
|
||||||
}
|
}
|
||||||
t.Printf(" Directory:\t%s\n", config.Confinement.Outer)
|
if config.Data != "" {
|
||||||
if config.Confinement.Sandbox != nil {
|
t.Printf(" Data:\t%s\n", config.Data)
|
||||||
sandbox := config.Confinement.Sandbox
|
}
|
||||||
if sandbox.Hostname != "" {
|
if config.Container != nil {
|
||||||
t.Printf(" Hostname:\t%q\n", sandbox.Hostname)
|
container := config.Container
|
||||||
|
if container.Hostname != "" {
|
||||||
|
t.Printf(" Hostname:\t%s\n", container.Hostname)
|
||||||
}
|
}
|
||||||
flags := make([]string, 0, 7)
|
flags := make([]string, 0, 7)
|
||||||
writeFlag := func(name string, value bool) {
|
writeFlag := func(name string, value bool) {
|
||||||
@ -89,38 +91,40 @@ func printShowInstance(
|
|||||||
flags = append(flags, name)
|
flags = append(flags, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeFlag("userns", sandbox.Userns)
|
writeFlag("userns", container.Userns)
|
||||||
writeFlag("net", sandbox.Net)
|
writeFlag("devel", container.Devel)
|
||||||
writeFlag("dev", sandbox.Dev)
|
writeFlag("net", container.Net)
|
||||||
writeFlag("tty", sandbox.Tty)
|
writeFlag("device", container.Device)
|
||||||
writeFlag("mapuid", sandbox.MapRealUID)
|
writeFlag("tty", container.Tty)
|
||||||
writeFlag("directwl", sandbox.DirectWayland)
|
writeFlag("mapuid", container.MapRealUID)
|
||||||
writeFlag("autoetc", sandbox.AutoEtc)
|
writeFlag("directwl", config.DirectWayland)
|
||||||
|
writeFlag("autoetc", container.AutoEtc)
|
||||||
if len(flags) == 0 {
|
if len(flags) == 0 {
|
||||||
flags = append(flags, "none")
|
flags = append(flags, "none")
|
||||||
}
|
}
|
||||||
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
||||||
|
|
||||||
etc := sandbox.Etc
|
etc := container.Etc
|
||||||
if etc == "" {
|
if etc == "" {
|
||||||
etc = "/etc"
|
etc = "/etc"
|
||||||
}
|
}
|
||||||
t.Printf(" Etc:\t%s\n", etc)
|
t.Printf(" Etc:\t%s\n", etc)
|
||||||
|
|
||||||
if len(sandbox.Cover) > 0 {
|
if len(container.Cover) > 0 {
|
||||||
t.Printf(" Cover:\t%s\n", strings.Join(sandbox.Cover, " "))
|
t.Printf(" Cover:\t%s\n", strings.Join(container.Cover, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Env map[string]string `json:"env"`
|
t.Printf(" Path:\t%s\n", config.Path)
|
||||||
// Link [][2]string `json:"symlink"`
|
}
|
||||||
|
if len(config.Args) > 0 {
|
||||||
|
t.Printf(" Arguments:\t%s\n", strings.Join(config.Args, " "))
|
||||||
}
|
}
|
||||||
t.Printf(" Command:\t%s\n", strings.Join(config.Args, " "))
|
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
|
|
||||||
if !short {
|
if !short {
|
||||||
if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 {
|
if config.Container != nil && len(config.Container.Filesystem) > 0 {
|
||||||
t.Printf("Filesystem\n")
|
t.Printf("Filesystem\n")
|
||||||
for _, f := range config.Confinement.Sandbox.Filesystem {
|
for _, f := range config.Container.Filesystem {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -148,9 +152,9 @@ func printShowInstance(
|
|||||||
}
|
}
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
if len(config.Confinement.ExtraPerms) > 0 {
|
if len(config.ExtraPerms) > 0 {
|
||||||
t.Printf("Extra ACL\n")
|
t.Printf("Extra ACL\n")
|
||||||
for _, p := range config.Confinement.ExtraPerms {
|
for _, p := range config.ExtraPerms {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -178,14 +182,14 @@ func printShowInstance(
|
|||||||
t.Printf(" Broadcast:\t%q\n", c.Broadcast)
|
t.Printf(" Broadcast:\t%q\n", c.Broadcast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.Confinement.SessionBus != nil {
|
if config.SessionBus != nil {
|
||||||
t.Printf("Session bus\n")
|
t.Printf("Session bus\n")
|
||||||
printDBus(config.Confinement.SessionBus)
|
printDBus(config.SessionBus)
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
if config.Confinement.SystemBus != nil {
|
if config.SystemBus != nil {
|
||||||
t.Printf("System bus\n")
|
t.Printf("System bus\n")
|
||||||
printDBus(config.Confinement.SystemBus)
|
printDBus(config.SystemBus)
|
||||||
t.Printf("\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,22 +251,26 @@ func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON boo
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
t.Println("\tInstance\tPID\tApp\tUptime\tEnablements\tCommand")
|
t.Println("\tInstance\tPID\tApplication\tUptime")
|
||||||
for _, e := range exp {
|
for _, e := range exp {
|
||||||
var (
|
if len(e.s) != 1<<5 {
|
||||||
es = "(No confinement information)"
|
// unreachable
|
||||||
cs = "(No command information)"
|
log.Printf("possible store corruption: invalid instance string %s", e.s)
|
||||||
as = "(No configuration information)"
|
continue
|
||||||
)
|
}
|
||||||
|
|
||||||
|
as := "(No configuration information)"
|
||||||
if e.Config != nil {
|
if e.Config != nil {
|
||||||
es = e.Config.Confinement.Enablements.String()
|
as = strconv.Itoa(e.Config.Identity)
|
||||||
cs = fmt.Sprintf("%q", e.Config.Args)
|
id := e.Config.ID
|
||||||
as = strconv.Itoa(e.Config.Confinement.AppID)
|
if id == "" {
|
||||||
|
id = "uk.gensokyo.fortify." + e.s[:8]
|
||||||
}
|
}
|
||||||
t.Printf("\t%s\t%d\t%s\t%s\t%s\t%s\n",
|
as += " (" + id + ")"
|
||||||
e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String(), strings.TrimPrefix(es, ", "), cs)
|
}
|
||||||
|
t.Printf("\t%s\t%d\t%s\t%s\n",
|
||||||
|
e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String())
|
||||||
}
|
}
|
||||||
t.Println()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type expandedStateEntry struct {
|
type expandedStateEntry struct {
|
||||||
|
422
print_test.go
422
print_test.go
@ -7,11 +7,12 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testID = fst.ID{
|
testID = app.ID{
|
||||||
0x8e, 0x2c, 0x76, 0xb0,
|
0x8e, 0x2c, 0x76, 0xb0,
|
||||||
0x66, 0xda, 0xbe, 0x57,
|
0x66, 0xda, 0xbe, 0x57,
|
||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
@ -38,13 +39,14 @@ func Test_printShowInstance(t *testing.T) {
|
|||||||
{"config", nil, fst.Template(), false, false, `App
|
{"config", nil, fst.Template(), false, false, `App
|
||||||
ID: 9 (org.chromium.Chromium)
|
ID: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pulseaudio
|
Enablements: wayland, dbus, pulseaudio
|
||||||
Groups: ["video"]
|
Groups: video, dialout, plugdev
|
||||||
Directory: /var/lib/persist/home/org.chromium.Chromium
|
Data: /var/lib/fortify/u0/org.chromium.Chromium
|
||||||
Hostname: "localhost"
|
Hostname: localhost
|
||||||
Flags: userns net dev tty mapuid autoetc
|
Flags: userns devel net device tty mapuid autoetc
|
||||||
Etc: /etc
|
Etc: /etc
|
||||||
Cover: /var/run/nscd
|
Cover: /var/run/nscd
|
||||||
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
Path: /run/current-system/sw/bin/chromium
|
||||||
|
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||||
|
|
||||||
Filesystem
|
Filesystem
|
||||||
+/nix/store
|
+/nix/store
|
||||||
@ -75,39 +77,33 @@ System bus
|
|||||||
App
|
App
|
||||||
ID: 0
|
ID: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Directory:
|
|
||||||
Command:
|
|
||||||
|
|
||||||
`},
|
`},
|
||||||
{"config flag none", nil, &fst.Config{Confinement: fst.ConfinementConfig{Sandbox: new(fst.SandboxConfig)}}, false, false, `App
|
{"config flag none", nil, &fst.Config{Container: new(fst.ContainerConfig)}, false, false, `App
|
||||||
ID: 0
|
ID: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Directory:
|
|
||||||
Flags: none
|
Flags: none
|
||||||
Etc: /etc
|
Etc: /etc
|
||||||
Command:
|
Path:
|
||||||
|
|
||||||
`},
|
`},
|
||||||
{"config nil entries", nil, &fst.Config{Confinement: fst.ConfinementConfig{Sandbox: &fst.SandboxConfig{Filesystem: make([]*fst.FilesystemConfig, 1)}, ExtraPerms: make([]*fst.ExtraPermConfig, 1)}}, false, false, `App
|
{"config nil entries", nil, &fst.Config{Container: &fst.ContainerConfig{Filesystem: make([]*fst.FilesystemConfig, 1)}, ExtraPerms: make([]*fst.ExtraPermConfig, 1)}, false, false, `App
|
||||||
ID: 0
|
ID: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Directory:
|
|
||||||
Flags: none
|
Flags: none
|
||||||
Etc: /etc
|
Etc: /etc
|
||||||
Command:
|
Path:
|
||||||
|
|
||||||
Filesystem
|
Filesystem
|
||||||
|
|
||||||
Extra ACL
|
Extra ACL
|
||||||
|
|
||||||
`},
|
`},
|
||||||
{"config pd dbus see", nil, &fst.Config{Confinement: fst.ConfinementConfig{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}}, false, false, `Warning: this configuration uses permissive defaults!
|
{"config pd dbus see", nil, &fst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Warning: this configuration uses permissive defaults!
|
||||||
|
|
||||||
App
|
App
|
||||||
ID: 0
|
ID: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Directory:
|
|
||||||
Command:
|
|
||||||
|
|
||||||
Session bus
|
Session bus
|
||||||
Filter: false
|
Filter: false
|
||||||
@ -122,13 +118,14 @@ Session bus
|
|||||||
App
|
App
|
||||||
ID: 9 (org.chromium.Chromium)
|
ID: 9 (org.chromium.Chromium)
|
||||||
Enablements: wayland, dbus, pulseaudio
|
Enablements: wayland, dbus, pulseaudio
|
||||||
Groups: ["video"]
|
Groups: video, dialout, plugdev
|
||||||
Directory: /var/lib/persist/home/org.chromium.Chromium
|
Data: /var/lib/fortify/u0/org.chromium.Chromium
|
||||||
Hostname: "localhost"
|
Hostname: localhost
|
||||||
Flags: userns net dev tty mapuid autoetc
|
Flags: userns devel net device tty mapuid autoetc
|
||||||
Etc: /etc
|
Etc: /etc
|
||||||
Cover: /var/run/nscd
|
Cover: /var/run/nscd
|
||||||
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
Path: /run/current-system/sw/bin/chromium
|
||||||
|
Arguments: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||||
|
|
||||||
Filesystem
|
Filesystem
|
||||||
+/nix/store
|
+/nix/store
|
||||||
@ -163,8 +160,6 @@ State
|
|||||||
App
|
App
|
||||||
ID: 0
|
ID: 0
|
||||||
Enablements: (no enablements)
|
Enablements: (no enablements)
|
||||||
Directory:
|
|
||||||
Command:
|
|
||||||
|
|
||||||
`},
|
`},
|
||||||
|
|
||||||
@ -200,15 +195,67 @@ App
|
|||||||
"--enable-features=UseOzonePlatform",
|
"--enable-features=UseOzonePlatform",
|
||||||
"--ozone-platform=wayland"
|
"--ozone-platform=wayland"
|
||||||
],
|
],
|
||||||
"confinement": {
|
"enablements": 13,
|
||||||
"app_id": 9,
|
"session_bus": {
|
||||||
"groups": [
|
"see": null,
|
||||||
"video"
|
"talk": [
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager"
|
||||||
],
|
],
|
||||||
|
"own": [
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"
|
||||||
|
],
|
||||||
|
"call": {
|
||||||
|
"org.freedesktop.portal.*": "*"
|
||||||
|
},
|
||||||
|
"broadcast": {
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
||||||
|
},
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
|
"system_bus": {
|
||||||
|
"see": null,
|
||||||
|
"talk": [
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower"
|
||||||
|
],
|
||||||
|
"own": null,
|
||||||
|
"call": null,
|
||||||
|
"broadcast": null,
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
"username": "chronos",
|
"username": "chronos",
|
||||||
"home_inner": "/var/lib/fortify",
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
"data": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
"sandbox": {
|
"dir": "/data/data/org.chromium.Chromium",
|
||||||
|
"extra_perms": [
|
||||||
|
{
|
||||||
|
"ensure": true,
|
||||||
|
"path": "/var/lib/fortify/u0",
|
||||||
|
"x": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"r": true,
|
||||||
|
"w": true,
|
||||||
|
"x": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identity": 9,
|
||||||
|
"groups": [
|
||||||
|
"video",
|
||||||
|
"dialout",
|
||||||
|
"plugdev"
|
||||||
|
],
|
||||||
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"seccomp": 32,
|
"seccomp": 32,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
@ -222,7 +269,7 @@ App
|
|||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
},
|
},
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"dev": true,
|
"device": true,
|
||||||
"filesystem": [
|
"filesystem": [
|
||||||
{
|
{
|
||||||
"src": "/nix/store"
|
"src": "/nix/store"
|
||||||
@ -258,57 +305,6 @@ App
|
|||||||
"cover": [
|
"cover": [
|
||||||
"/var/run/nscd"
|
"/var/run/nscd"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"extra_perms": [
|
|
||||||
{
|
|
||||||
"ensure": true,
|
|
||||||
"path": "/var/lib/fortify/u0",
|
|
||||||
"x": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
|
||||||
"r": true,
|
|
||||||
"w": true,
|
|
||||||
"x": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"system_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower"
|
|
||||||
],
|
|
||||||
"own": null,
|
|
||||||
"call": null,
|
|
||||||
"broadcast": null,
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"session_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager"
|
|
||||||
],
|
|
||||||
"own": [
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
],
|
|
||||||
"call": {
|
|
||||||
"org.freedesktop.portal.*": "*"
|
|
||||||
},
|
|
||||||
"broadcast": {
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
|
||||||
},
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"enablements": 13
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": "1970-01-01T00:00:00.000000009Z"
|
"time": "1970-01-01T00:00:00.000000009Z"
|
||||||
@ -324,15 +320,67 @@ App
|
|||||||
"--enable-features=UseOzonePlatform",
|
"--enable-features=UseOzonePlatform",
|
||||||
"--ozone-platform=wayland"
|
"--ozone-platform=wayland"
|
||||||
],
|
],
|
||||||
"confinement": {
|
"enablements": 13,
|
||||||
"app_id": 9,
|
"session_bus": {
|
||||||
"groups": [
|
"see": null,
|
||||||
"video"
|
"talk": [
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager"
|
||||||
],
|
],
|
||||||
|
"own": [
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"
|
||||||
|
],
|
||||||
|
"call": {
|
||||||
|
"org.freedesktop.portal.*": "*"
|
||||||
|
},
|
||||||
|
"broadcast": {
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
||||||
|
},
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
|
"system_bus": {
|
||||||
|
"see": null,
|
||||||
|
"talk": [
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower"
|
||||||
|
],
|
||||||
|
"own": null,
|
||||||
|
"call": null,
|
||||||
|
"broadcast": null,
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
"username": "chronos",
|
"username": "chronos",
|
||||||
"home_inner": "/var/lib/fortify",
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
"data": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
"sandbox": {
|
"dir": "/data/data/org.chromium.Chromium",
|
||||||
|
"extra_perms": [
|
||||||
|
{
|
||||||
|
"ensure": true,
|
||||||
|
"path": "/var/lib/fortify/u0",
|
||||||
|
"x": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"r": true,
|
||||||
|
"w": true,
|
||||||
|
"x": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identity": 9,
|
||||||
|
"groups": [
|
||||||
|
"video",
|
||||||
|
"dialout",
|
||||||
|
"plugdev"
|
||||||
|
],
|
||||||
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"seccomp": 32,
|
"seccomp": 32,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
@ -346,7 +394,7 @@ App
|
|||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
},
|
},
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"dev": true,
|
"device": true,
|
||||||
"filesystem": [
|
"filesystem": [
|
||||||
{
|
{
|
||||||
"src": "/nix/store"
|
"src": "/nix/store"
|
||||||
@ -382,57 +430,6 @@ App
|
|||||||
"cover": [
|
"cover": [
|
||||||
"/var/run/nscd"
|
"/var/run/nscd"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"extra_perms": [
|
|
||||||
{
|
|
||||||
"ensure": true,
|
|
||||||
"path": "/var/lib/fortify/u0",
|
|
||||||
"x": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
|
||||||
"r": true,
|
|
||||||
"w": true,
|
|
||||||
"x": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"system_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower"
|
|
||||||
],
|
|
||||||
"own": null,
|
|
||||||
"call": null,
|
|
||||||
"broadcast": null,
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"session_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager"
|
|
||||||
],
|
|
||||||
"own": [
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
],
|
|
||||||
"call": {
|
|
||||||
"org.freedesktop.portal.*": "*"
|
|
||||||
},
|
|
||||||
"broadcast": {
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
|
||||||
},
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"enablements": 13
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`},
|
`},
|
||||||
@ -458,23 +455,19 @@ func Test_printPs(t *testing.T) {
|
|||||||
short, json bool
|
short, json bool
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"no entries", make(state.Entries), false, false, ` Instance PID App Uptime Enablements Command
|
{"no entries", make(state.Entries), false, false, " Instance PID Application Uptime\n"},
|
||||||
|
{"no entries short", make(state.Entries), true, false, ""},
|
||||||
`},
|
{"nil instance", state.Entries{testID: nil}, false, false, " Instance PID Application Uptime\n"},
|
||||||
{"no entries short", make(state.Entries), true, false, ``},
|
{"state corruption", state.Entries{app.ID{}: testState}, false, false, " Instance PID Application Uptime\n"},
|
||||||
{"nil instance", state.Entries{testID: nil}, false, false, ` Instance PID App Uptime Enablements Command
|
|
||||||
|
|
||||||
`},
|
|
||||||
{"state corruption", state.Entries{fst.ID{}: testState}, false, false, ` Instance PID App Uptime Enablements Command
|
|
||||||
|
|
||||||
|
{"valid pd", state.Entries{testID: &state.State{ID: testID, PID: 1 << 8, Config: new(fst.Config), Time: testAppTime}}, false, false, ` Instance PID Application Uptime
|
||||||
|
8e2c76b0 256 0 (uk.gensokyo.fortify.8e2c76b0) 1h2m32s
|
||||||
`},
|
`},
|
||||||
|
|
||||||
{"valid", state.Entries{testID: testState}, false, false, ` Instance PID App Uptime Enablements Command
|
{"valid", state.Entries{testID: testState}, false, false, ` Instance PID Application Uptime
|
||||||
8e2c76b0 3735928559 9 1h2m32s wayland, dbus, pulseaudio ["chromium" "--ignore-gpu-blocklist" "--disable-smooth-scrolling" "--enable-features=UseOzonePlatform" "--ozone-platform=wayland"]
|
8e2c76b0 3735928559 9 (org.chromium.Chromium) 1h2m32s
|
||||||
|
|
||||||
`},
|
|
||||||
{"valid short", state.Entries{testID: testState}, true, false, `8e2c76b0
|
|
||||||
`},
|
`},
|
||||||
|
{"valid short", state.Entries{testID: testState}, true, false, "8e2c76b0\n"},
|
||||||
{"valid json", state.Entries{testID: testState}, false, true, `{
|
{"valid json", state.Entries{testID: testState}, false, true, `{
|
||||||
"8e2c76b066dabe574cf073bdb46eb5c1": {
|
"8e2c76b066dabe574cf073bdb46eb5c1": {
|
||||||
"instance": [
|
"instance": [
|
||||||
@ -506,15 +499,67 @@ func Test_printPs(t *testing.T) {
|
|||||||
"--enable-features=UseOzonePlatform",
|
"--enable-features=UseOzonePlatform",
|
||||||
"--ozone-platform=wayland"
|
"--ozone-platform=wayland"
|
||||||
],
|
],
|
||||||
"confinement": {
|
"enablements": 13,
|
||||||
"app_id": 9,
|
"session_bus": {
|
||||||
"groups": [
|
"see": null,
|
||||||
"video"
|
"talk": [
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"org.freedesktop.FileManager1",
|
||||||
|
"org.freedesktop.ScreenSaver",
|
||||||
|
"org.freedesktop.secrets",
|
||||||
|
"org.kde.kwalletd5",
|
||||||
|
"org.kde.kwalletd6",
|
||||||
|
"org.gnome.SessionManager"
|
||||||
],
|
],
|
||||||
|
"own": [
|
||||||
|
"org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
||||||
|
"org.mpris.MediaPlayer2.chromium.*"
|
||||||
|
],
|
||||||
|
"call": {
|
||||||
|
"org.freedesktop.portal.*": "*"
|
||||||
|
},
|
||||||
|
"broadcast": {
|
||||||
|
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
||||||
|
},
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
|
"system_bus": {
|
||||||
|
"see": null,
|
||||||
|
"talk": [
|
||||||
|
"org.bluez",
|
||||||
|
"org.freedesktop.Avahi",
|
||||||
|
"org.freedesktop.UPower"
|
||||||
|
],
|
||||||
|
"own": null,
|
||||||
|
"call": null,
|
||||||
|
"broadcast": null,
|
||||||
|
"filter": true
|
||||||
|
},
|
||||||
"username": "chronos",
|
"username": "chronos",
|
||||||
"home_inner": "/var/lib/fortify",
|
"shell": "/run/current-system/sw/bin/zsh",
|
||||||
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
"data": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
"sandbox": {
|
"dir": "/data/data/org.chromium.Chromium",
|
||||||
|
"extra_perms": [
|
||||||
|
{
|
||||||
|
"ensure": true,
|
||||||
|
"path": "/var/lib/fortify/u0",
|
||||||
|
"x": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
||||||
|
"r": true,
|
||||||
|
"w": true,
|
||||||
|
"x": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identity": 9,
|
||||||
|
"groups": [
|
||||||
|
"video",
|
||||||
|
"dialout",
|
||||||
|
"plugdev"
|
||||||
|
],
|
||||||
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
"seccomp": 32,
|
"seccomp": 32,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
@ -528,7 +573,7 @@ func Test_printPs(t *testing.T) {
|
|||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
},
|
},
|
||||||
"map_real_uid": true,
|
"map_real_uid": true,
|
||||||
"dev": true,
|
"device": true,
|
||||||
"filesystem": [
|
"filesystem": [
|
||||||
{
|
{
|
||||||
"src": "/nix/store"
|
"src": "/nix/store"
|
||||||
@ -564,57 +609,6 @@ func Test_printPs(t *testing.T) {
|
|||||||
"cover": [
|
"cover": [
|
||||||
"/var/run/nscd"
|
"/var/run/nscd"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"extra_perms": [
|
|
||||||
{
|
|
||||||
"ensure": true,
|
|
||||||
"path": "/var/lib/fortify/u0",
|
|
||||||
"x": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/var/lib/fortify/u0/org.chromium.Chromium",
|
|
||||||
"r": true,
|
|
||||||
"w": true,
|
|
||||||
"x": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"system_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower"
|
|
||||||
],
|
|
||||||
"own": null,
|
|
||||||
"call": null,
|
|
||||||
"broadcast": null,
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"session_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager"
|
|
||||||
],
|
|
||||||
"own": [
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
],
|
|
||||||
"call": {
|
|
||||||
"org.freedesktop.portal.*": "*"
|
|
||||||
},
|
|
||||||
"broadcast": {
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
|
||||||
},
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"enablements": 13
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": "1970-01-01T00:00:00.000000009Z"
|
"time": "1970-01-01T00:00:00.000000009Z"
|
||||||
|
@ -27,18 +27,18 @@ const (
|
|||||||
FAllowNet
|
FAllowNet
|
||||||
)
|
)
|
||||||
|
|
||||||
func (flags HardeningFlags) seccomp(opts seccomp.SyscallOpts) seccomp.SyscallOpts {
|
func (flags HardeningFlags) seccomp(opts seccomp.FilterOpts) seccomp.FilterOpts {
|
||||||
if flags&FSyscallCompat == 0 {
|
if flags&FSyscallCompat == 0 {
|
||||||
opts |= seccomp.FlagExt
|
opts |= seccomp.FilterExt
|
||||||
}
|
}
|
||||||
if flags&FAllowDevel == 0 {
|
if flags&FAllowDevel == 0 {
|
||||||
opts |= seccomp.FlagDenyDevel
|
opts |= seccomp.FilterDenyDevel
|
||||||
}
|
}
|
||||||
if flags&FAllowUserns == 0 {
|
if flags&FAllowUserns == 0 {
|
||||||
opts |= seccomp.FlagDenyNS
|
opts |= seccomp.FilterDenyNS
|
||||||
}
|
}
|
||||||
if flags&FAllowTTY == 0 {
|
if flags&FAllowTTY == 0 {
|
||||||
opts |= seccomp.FlagDenyTTY
|
opts |= seccomp.FilterDenyTTY
|
||||||
}
|
}
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ type (
|
|||||||
// Sequential container setup ops.
|
// Sequential container setup ops.
|
||||||
*Ops
|
*Ops
|
||||||
// Extra seccomp options.
|
// Extra seccomp options.
|
||||||
Seccomp seccomp.SyscallOpts
|
Seccomp seccomp.FilterOpts
|
||||||
// Permission bits of newly created parent directories.
|
// Permission bits of newly created parent directories.
|
||||||
// The zero value is interpreted as 0755.
|
// The zero value is interpreted as 0755.
|
||||||
ParentPerm os.FileMode
|
ParentPerm os.FileMode
|
||||||
@ -104,16 +104,6 @@ type (
|
|||||||
|
|
||||||
Flags HardeningFlags
|
Flags HardeningFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
Ops []Op
|
|
||||||
Op interface {
|
|
||||||
early(params *Params) error
|
|
||||||
apply(params *Params) error
|
|
||||||
prefix() string
|
|
||||||
|
|
||||||
Is(op Op) bool
|
|
||||||
fmt.Stringer
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Container) Start() error {
|
func (p *Container) Start() error {
|
||||||
|
@ -164,7 +164,7 @@ func e(root, target, vfsOptstr, fsType, source, fsOptstr string) *vfs.MountInfoE
|
|||||||
func TestContainerString(t *testing.T) {
|
func TestContainerString(t *testing.T) {
|
||||||
container := sandbox.New(context.TODO(), "ldd", "/usr/bin/env")
|
container := sandbox.New(context.TODO(), "ldd", "/usr/bin/env")
|
||||||
container.Flags |= sandbox.FAllowDevel
|
container.Flags |= sandbox.FAllowDevel
|
||||||
container.Seccomp |= seccomp.FlagMultiarch
|
container.Seccomp |= seccomp.FilterMultiarch
|
||||||
want := `argv: ["ldd" "/usr/bin/env"], flags: 0x2, seccomp: 0x2e`
|
want := `argv: ["ldd" "/usr/bin/env"], flags: 0x2, seccomp: 0x2e`
|
||||||
if got := container.String(); got != want {
|
if got := container.String(); got != want {
|
||||||
t.Errorf("String: %s, want %s", got, want)
|
t.Errorf("String: %s, want %s", got, want)
|
||||||
|
@ -45,10 +45,6 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
log.Fatal("this process must run as pid 1")
|
log.Fatal("this process must run as pid 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
receive setup payload
|
|
||||||
*/
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
params initParams
|
params initParams
|
||||||
closeSetup func() error
|
closeSetup func() error
|
||||||
@ -111,10 +107,6 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
// cache sysctl before pivot_root
|
// cache sysctl before pivot_root
|
||||||
LastCap()
|
LastCap()
|
||||||
|
|
||||||
/*
|
|
||||||
set up mount points from intermediate root
|
|
||||||
*/
|
|
||||||
|
|
||||||
if err := syscall.Mount("", "/", "",
|
if err := syscall.Mount("", "/", "",
|
||||||
syscall.MS_SILENT|syscall.MS_SLAVE|syscall.MS_REC,
|
syscall.MS_SILENT|syscall.MS_SLAVE|syscall.MS_REC,
|
||||||
""); err != nil {
|
""); err != nil {
|
||||||
@ -155,6 +147,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
if err := os.Mkdir(hostDir, 0755); err != nil {
|
if err := os.Mkdir(hostDir, 0755); err != nil {
|
||||||
log.Fatalf("%v", err)
|
log.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
// pivot_root uncovers basePath in hostDir
|
||||||
if err := syscall.PivotRoot(basePath, hostDir); err != nil {
|
if err := syscall.PivotRoot(basePath, hostDir); err != nil {
|
||||||
log.Fatalf("cannot pivot into intermediate root: %v", err)
|
log.Fatalf("cannot pivot into intermediate root: %v", err)
|
||||||
}
|
}
|
||||||
@ -173,10 +166,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// setup requiring host root complete at this point
|
||||||
pivot to sysroot
|
|
||||||
*/
|
|
||||||
|
|
||||||
if err := syscall.Mount(hostDir, hostDir, "",
|
if err := syscall.Mount(hostDir, hostDir, "",
|
||||||
syscall.MS_SILENT|syscall.MS_REC|syscall.MS_PRIVATE,
|
syscall.MS_SILENT|syscall.MS_REC|syscall.MS_PRIVATE,
|
||||||
""); err != nil {
|
""); err != nil {
|
||||||
@ -216,10 +206,6 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
caps/securebits and seccomp filter
|
|
||||||
*/
|
|
||||||
|
|
||||||
if _, _, errno := syscall.Syscall(PR_SET_NO_NEW_PRIVS, 1, 0, 0); errno != 0 {
|
if _, _, errno := syscall.Syscall(PR_SET_NO_NEW_PRIVS, 1, 0, 0); errno != 0 {
|
||||||
log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", errno)
|
log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", errno)
|
||||||
}
|
}
|
||||||
@ -255,20 +241,13 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
log.Fatalf("cannot load syscall filter: %v", err)
|
log.Fatalf("cannot load syscall filter: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
pass through extra files
|
|
||||||
*/
|
|
||||||
|
|
||||||
extraFiles := make([]*os.File, params.Count)
|
extraFiles := make([]*os.File, params.Count)
|
||||||
for i := range extraFiles {
|
for i := range extraFiles {
|
||||||
|
// setup fd is placed before all extra files
|
||||||
extraFiles[i] = os.NewFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i))
|
extraFiles[i] = os.NewFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
syscall.Umask(oldmask)
|
syscall.Umask(oldmask)
|
||||||
|
|
||||||
/*
|
|
||||||
prepare initial process
|
|
||||||
*/
|
|
||||||
|
|
||||||
cmd := exec.Command(params.Path)
|
cmd := exec.Command(params.Path)
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
cmd.Args = params.Args
|
cmd.Args = params.Args
|
||||||
@ -281,22 +260,11 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
}
|
}
|
||||||
msg.Suspend()
|
msg.Suspend()
|
||||||
|
|
||||||
/*
|
|
||||||
close setup pipe
|
|
||||||
*/
|
|
||||||
|
|
||||||
if err := closeSetup(); err != nil {
|
if err := closeSetup(); err != nil {
|
||||||
log.Println("cannot close setup pipe:", err)
|
log.Println("cannot close setup pipe:", err)
|
||||||
// not fatal
|
// not fatal
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
perform init duties
|
|
||||||
*/
|
|
||||||
|
|
||||||
sig := make(chan os.Signal, 2)
|
|
||||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
type winfo struct {
|
type winfo struct {
|
||||||
wpid int
|
wpid int
|
||||||
wstatus syscall.WaitStatus
|
wstatus syscall.WaitStatus
|
||||||
@ -333,6 +301,10 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// handle signals to dump withheld messages
|
||||||
|
sig := make(chan os.Signal, 2)
|
||||||
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
// closed after residualProcessTimeout has elapsed after initial process death
|
// closed after residualProcessTimeout has elapsed after initial process death
|
||||||
timeout := make(chan struct{})
|
timeout := make(chan struct{})
|
||||||
|
|
||||||
@ -345,7 +317,6 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
|
|||||||
} else {
|
} else {
|
||||||
msg.Verbosef("terminating on %s", s.String())
|
msg.Verbosef("terminating on %s", s.String())
|
||||||
}
|
}
|
||||||
msg.BeforeExit()
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
|
@ -13,6 +13,22 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Ops []Op
|
||||||
|
Op interface {
|
||||||
|
// early is called in host root.
|
||||||
|
early(params *Params) error
|
||||||
|
// apply is called in intermediate root.
|
||||||
|
apply(params *Params) error
|
||||||
|
|
||||||
|
prefix() string
|
||||||
|
Is(op Op) bool
|
||||||
|
fmt.Stringer
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Ops) Grow(n int) { *f = slices.Grow(*f, n) }
|
||||||
|
|
||||||
func init() { gob.Register(new(BindMount)) }
|
func init() { gob.Register(new(BindMount)) }
|
||||||
|
|
||||||
// BindMount bind mounts host path Source on container path Target.
|
// BindMount bind mounts host path Source on container path Target.
|
||||||
@ -424,3 +440,60 @@ func (f *Ops) PlaceP(name string, dataP **[]byte) *Ops {
|
|||||||
*f = append(*f, t)
|
*f = append(*f, t)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() { gob.Register(new(AutoEtc)) }
|
||||||
|
|
||||||
|
// AutoEtc expands host /etc into a toplevel symlink mirror with /etc semantics.
|
||||||
|
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
|
||||||
|
type AutoEtc struct{ Prefix string }
|
||||||
|
|
||||||
|
func (e *AutoEtc) early(*Params) error { return nil }
|
||||||
|
func (e *AutoEtc) apply(*Params) error {
|
||||||
|
const target = sysrootPath + "/etc/"
|
||||||
|
rel := e.hostRel() + "/"
|
||||||
|
|
||||||
|
if err := os.MkdirAll(target, 0755); err != nil {
|
||||||
|
return wrapErrSelf(err)
|
||||||
|
}
|
||||||
|
if d, err := os.ReadDir(toSysroot(e.hostPath())); err != nil {
|
||||||
|
return wrapErrSelf(err)
|
||||||
|
} else {
|
||||||
|
for _, ent := range d {
|
||||||
|
n := ent.Name()
|
||||||
|
switch n {
|
||||||
|
case ".host":
|
||||||
|
|
||||||
|
case "passwd":
|
||||||
|
case "group":
|
||||||
|
|
||||||
|
case "mtab":
|
||||||
|
if err = os.Symlink("/proc/mounts", target+n); err != nil {
|
||||||
|
return wrapErrSelf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err = os.Symlink(rel+n, target+n); err != nil {
|
||||||
|
return wrapErrSelf(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (e *AutoEtc) hostPath() string { return "/etc/" + e.hostRel() }
|
||||||
|
func (e *AutoEtc) hostRel() string { return ".host/" + e.Prefix }
|
||||||
|
|
||||||
|
func (e *AutoEtc) Is(op Op) bool {
|
||||||
|
ve, ok := op.(*AutoEtc)
|
||||||
|
return ok && ((e == nil && ve == nil) || (e != nil && ve != nil && *e == *ve))
|
||||||
|
}
|
||||||
|
func (*AutoEtc) prefix() string { return "setting up" }
|
||||||
|
func (e *AutoEtc) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) }
|
||||||
|
func (f *Ops) Etc(host, prefix string) *Ops {
|
||||||
|
e := &AutoEtc{prefix}
|
||||||
|
f.Mkdir("/etc", 0755)
|
||||||
|
f.Bind(host, e.hostPath(), 0)
|
||||||
|
*f = append(*f, e)
|
||||||
|
return f
|
||||||
|
}
|
@ -8,11 +8,16 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PresetStrict = FilterExt | FilterDenyNS | FilterDenyTTY | FilterDenyDevel
|
||||||
|
PresetCommon = PresetStrict | FilterMultiarch
|
||||||
|
)
|
||||||
|
|
||||||
// New returns an inactive Encoder instance.
|
// New returns an inactive Encoder instance.
|
||||||
func New(opts SyscallOpts) *Encoder { return &Encoder{newExporter(opts)} }
|
func New(opts FilterOpts) *Encoder { return &Encoder{newExporter(opts)} }
|
||||||
|
|
||||||
// Load loads a filter into the kernel.
|
// Load loads a filter into the kernel.
|
||||||
func Load(opts SyscallOpts) error { return buildFilter(-1, opts) }
|
func Load(opts FilterOpts) error { return buildFilter(-1, opts) }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
An Encoder writes a BPF program to an output stream.
|
An Encoder writes a BPF program to an output stream.
|
||||||
@ -42,11 +47,11 @@ func (e *Encoder) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFile returns an instance of exporter implementing [proc.File].
|
// NewFile returns an instance of exporter implementing [proc.File].
|
||||||
func NewFile(opts SyscallOpts) proc.File { return &File{opts: opts} }
|
func NewFile(opts FilterOpts) proc.File { return &File{opts: opts} }
|
||||||
|
|
||||||
// File implements [proc.File] and provides access to the read end of exporter pipe.
|
// File implements [proc.File] and provides access to the read end of exporter pipe.
|
||||||
type File struct {
|
type File struct {
|
||||||
opts SyscallOpts
|
opts FilterOpts
|
||||||
proc.BaseFile
|
proc.BaseFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type exporter struct {
|
type exporter struct {
|
||||||
opts SyscallOpts
|
opts FilterOpts
|
||||||
r, w *os.File
|
r, w *os.File
|
||||||
|
|
||||||
prepareOnce sync.Once
|
prepareOnce sync.Once
|
||||||
@ -53,6 +53,6 @@ func (e *exporter) closeWrite() error {
|
|||||||
return e.closeErr
|
return e.closeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newExporter(opts SyscallOpts) *exporter {
|
func newExporter(opts FilterOpts) *exporter {
|
||||||
return &exporter{opts: opts}
|
return &exporter{opts: opts}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
func TestExport(t *testing.T) {
|
func TestExport(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
opts seccomp.SyscallOpts
|
opts seccomp.FilterOpts
|
||||||
want []byte
|
want []byte
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
@ -28,7 +28,7 @@ func TestExport(t *testing.T) {
|
|||||||
0xa7, 0x9b, 0x07, 0x0e, 0x04, 0xc0, 0xee, 0x9a,
|
0xa7, 0x9b, 0x07, 0x0e, 0x04, 0xc0, 0xee, 0x9a,
|
||||||
0xcd, 0xf5, 0x8f, 0x55, 0xcf, 0xa8, 0x15, 0xa5,
|
0xcd, 0xf5, 0x8f, 0x55, 0xcf, 0xa8, 0x15, 0xa5,
|
||||||
}, false},
|
}, false},
|
||||||
{"base", seccomp.FlagExt, []byte{
|
{"base", seccomp.FilterExt, []byte{
|
||||||
0xdc, 0x7f, 0x2e, 0x1c, 0x5e, 0x82, 0x9b, 0x79,
|
0xdc, 0x7f, 0x2e, 0x1c, 0x5e, 0x82, 0x9b, 0x79,
|
||||||
0xeb, 0xb7, 0xef, 0xc7, 0x59, 0x15, 0x0f, 0x54,
|
0xeb, 0xb7, 0xef, 0xc7, 0x59, 0x15, 0x0f, 0x54,
|
||||||
0xa8, 0x3a, 0x75, 0xc8, 0xdf, 0x6f, 0xee, 0x4d,
|
0xa8, 0x3a, 0x75, 0xc8, 0xdf, 0x6f, 0xee, 0x4d,
|
||||||
@ -38,10 +38,10 @@ func TestExport(t *testing.T) {
|
|||||||
0x1d, 0xb0, 0x5d, 0x90, 0x99, 0x7c, 0x86, 0x59,
|
0x1d, 0xb0, 0x5d, 0x90, 0x99, 0x7c, 0x86, 0x59,
|
||||||
0xb9, 0x58, 0x91, 0x20, 0x6a, 0xc9, 0x95, 0x2d,
|
0xb9, 0x58, 0x91, 0x20, 0x6a, 0xc9, 0x95, 0x2d,
|
||||||
}, false},
|
}, false},
|
||||||
{"everything", seccomp.FlagExt |
|
{"everything", seccomp.FilterExt |
|
||||||
seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel |
|
seccomp.FilterDenyNS | seccomp.FilterDenyTTY | seccomp.FilterDenyDevel |
|
||||||
seccomp.FlagMultiarch | seccomp.FlagLinux32 | seccomp.FlagCan |
|
seccomp.FilterMultiarch | seccomp.FilterLinux32 | seccomp.FilterCan |
|
||||||
seccomp.FlagBluetooth, []byte{
|
seccomp.FilterBluetooth, []byte{
|
||||||
0xe9, 0x9d, 0xd3, 0x45, 0xe1, 0x95, 0x41, 0x34,
|
0xe9, 0x9d, 0xd3, 0x45, 0xe1, 0x95, 0x41, 0x34,
|
||||||
0x73, 0xd3, 0xcb, 0xee, 0x07, 0xb4, 0xed, 0x57,
|
0x73, 0xd3, 0xcb, 0xee, 0x07, 0xb4, 0xed, 0x57,
|
||||||
0xb9, 0x08, 0xbf, 0xa8, 0x9e, 0xa2, 0x07, 0x2f,
|
0xb9, 0x08, 0xbf, 0xa8, 0x9e, 0xa2, 0x07, 0x2f,
|
||||||
@ -51,8 +51,7 @@ func TestExport(t *testing.T) {
|
|||||||
0x4c, 0x02, 0x4e, 0xd4, 0x88, 0x50, 0xbe, 0x69,
|
0x4c, 0x02, 0x4e, 0xd4, 0x88, 0x50, 0xbe, 0x69,
|
||||||
0xb6, 0x8a, 0x9a, 0x4c, 0x5f, 0x53, 0xa9, 0xdb,
|
0xb6, 0x8a, 0x9a, 0x4c, 0x5f, 0x53, 0xa9, 0xdb,
|
||||||
}, false},
|
}, false},
|
||||||
{"strict", seccomp.FlagExt |
|
{"strict", seccomp.PresetStrict, []byte{
|
||||||
seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel, []byte{
|
|
||||||
0xe8, 0x80, 0x29, 0x8d, 0xf2, 0xbd, 0x67, 0x51,
|
0xe8, 0x80, 0x29, 0x8d, 0xf2, 0xbd, 0x67, 0x51,
|
||||||
0xd0, 0x04, 0x0f, 0xc2, 0x1b, 0xc0, 0xed, 0x4c,
|
0xd0, 0x04, 0x0f, 0xc2, 0x1b, 0xc0, 0xed, 0x4c,
|
||||||
0x00, 0xf9, 0x5d, 0xc0, 0xd7, 0xba, 0x50, 0x6c,
|
0x00, 0xf9, 0x5d, 0xc0, 0xd7, 0xba, 0x50, 0x6c,
|
||||||
@ -63,7 +62,7 @@ func TestExport(t *testing.T) {
|
|||||||
0x14, 0x89, 0x60, 0xfb, 0xd3, 0x5c, 0xd7, 0x35,
|
0x14, 0x89, 0x60, 0xfb, 0xd3, 0x5c, 0xd7, 0x35,
|
||||||
}, false},
|
}, false},
|
||||||
{"strict compat", 0 |
|
{"strict compat", 0 |
|
||||||
seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel, []byte{
|
seccomp.FilterDenyNS | seccomp.FilterDenyTTY | seccomp.FilterDenyDevel, []byte{
|
||||||
0x39, 0x87, 0x1b, 0x93, 0xff, 0xaf, 0xc8, 0xb9,
|
0x39, 0x87, 0x1b, 0x93, 0xff, 0xaf, 0xc8, 0xb9,
|
||||||
0x79, 0xfc, 0xed, 0xc0, 0xb0, 0xc3, 0x7b, 0x9e,
|
0x79, 0xfc, 0xed, 0xc0, 0xb0, 0xc3, 0x7b, 0x9e,
|
||||||
0x03, 0x92, 0x2f, 0x5b, 0x02, 0x74, 0x8d, 0xc5,
|
0x03, 0x92, 0x2f, 0x5b, 0x02, 0x74, 0x8d, 0xc5,
|
||||||
@ -73,7 +72,7 @@ func TestExport(t *testing.T) {
|
|||||||
0x80, 0x8b, 0x1a, 0x6f, 0x84, 0xf3, 0x2b, 0xbd,
|
0x80, 0x8b, 0x1a, 0x6f, 0x84, 0xf3, 0x2b, 0xbd,
|
||||||
0xe1, 0xaa, 0x02, 0xae, 0x30, 0xee, 0xdc, 0xfa,
|
0xe1, 0xaa, 0x02, 0xae, 0x30, 0xee, 0xdc, 0xfa,
|
||||||
}, false},
|
}, false},
|
||||||
{"fortify default", seccomp.FlagExt | seccomp.FlagDenyDevel, []byte{
|
{"fortify default", seccomp.FilterExt | seccomp.FilterDenyDevel, []byte{
|
||||||
0xc6, 0x98, 0xb0, 0x81, 0xff, 0x95, 0x7a, 0xfe,
|
0xc6, 0x98, 0xb0, 0x81, 0xff, 0x95, 0x7a, 0xfe,
|
||||||
0x17, 0xa6, 0xd9, 0x43, 0x74, 0x53, 0x7d, 0x37,
|
0x17, 0xa6, 0xd9, 0x43, 0x74, 0x53, 0x7d, 0x37,
|
||||||
0xf2, 0xa6, 0x3f, 0x6f, 0x9d, 0xd7, 0x5d, 0xa7,
|
0xf2, 0xa6, 0x3f, 0x6f, 0x9d, 0xd7, 0x5d, 0xa7,
|
||||||
@ -138,10 +137,10 @@ func TestExport(t *testing.T) {
|
|||||||
func BenchmarkExport(b *testing.B) {
|
func BenchmarkExport(b *testing.B) {
|
||||||
buf := make([]byte, 8)
|
buf := make([]byte, 8)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
e := seccomp.New(seccomp.FlagExt |
|
e := seccomp.New(seccomp.FilterExt |
|
||||||
seccomp.FlagDenyNS | seccomp.FlagDenyTTY | seccomp.FlagDenyDevel |
|
seccomp.FilterDenyNS | seccomp.FilterDenyTTY | seccomp.FilterDenyDevel |
|
||||||
seccomp.FlagMultiarch | seccomp.FlagLinux32 | seccomp.FlagCan |
|
seccomp.FilterMultiarch | seccomp.FilterLinux32 | seccomp.FilterCan |
|
||||||
seccomp.FlagBluetooth)
|
seccomp.FilterBluetooth)
|
||||||
if _, err := io.CopyBuffer(io.Discard, e, buf); err != nil {
|
if _, err := io.CopyBuffer(io.Discard, e, buf); err != nil {
|
||||||
b.Fatalf("cannot export: %v", err)
|
b.Fatalf("cannot export: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ func GetOutput() func(v ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export F_println
|
//export f_println
|
||||||
func F_println(v *C.char) {
|
func f_println(v *C.char) {
|
||||||
if fp := printlnP.Load(); fp != nil {
|
if fp := printlnP.Load(); fp != nil {
|
||||||
(*fp)(C.GoString(v))
|
(*fp)(C.GoString(v))
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ struct f_syscall_act {
|
|||||||
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
|
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||||
|
|
||||||
#define SECCOMP_RULESET_ADD(ruleset) do { \
|
#define SECCOMP_RULESET_ADD(ruleset) do { \
|
||||||
if (opts & F_VERBOSE) F_println("adding seccomp ruleset \"" #ruleset "\""); \
|
if (opts & F_VERBOSE) f_println("adding seccomp ruleset \"" #ruleset "\""); \
|
||||||
for (int i = 0; i < LEN(ruleset); i++) { \
|
for (int i = 0; i < LEN(ruleset); i++) { \
|
||||||
assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \
|
assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \
|
||||||
\
|
\
|
||||||
@ -47,7 +47,7 @@ struct f_syscall_act {
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts) {
|
int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_filter_opts opts) {
|
||||||
int32_t res = 0; // refer to resErr for meaning
|
int32_t res = 0; // refer to resErr for meaning
|
||||||
int allow_multiarch = opts & F_MULTIARCH;
|
int allow_multiarch = opts & F_MULTIARCH;
|
||||||
int allowed_personality = PER_LINUX;
|
int allowed_personality = PER_LINUX;
|
||||||
@ -209,7 +209,7 @@ int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_
|
|||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
int family;
|
int family;
|
||||||
f_syscall_opts flags_mask;
|
f_filter_opts flags_mask;
|
||||||
} socket_family_allowlist[] = {
|
} socket_family_allowlist[] = {
|
||||||
// NOTE: Keep in numerical order
|
// NOTE: Keep in numerical order
|
||||||
{ AF_UNSPEC, 0 },
|
{ AF_UNSPEC, 0 },
|
||||||
|
@ -17,7 +17,7 @@ typedef enum {
|
|||||||
F_LINUX32 = 1 << 6,
|
F_LINUX32 = 1 << 6,
|
||||||
F_CAN = 1 << 7,
|
F_CAN = 1 << 7,
|
||||||
F_BLUETOOTH = 1 << 8,
|
F_BLUETOOTH = 1 << 8,
|
||||||
} f_syscall_opts;
|
} f_filter_opts;
|
||||||
|
|
||||||
extern void F_println(char *v);
|
extern void f_println(char *v);
|
||||||
int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts opts);
|
int32_t f_build_filter(int *ret_p, int fd, uint32_t arch, uint32_t multiarch, f_filter_opts opts);
|
@ -7,6 +7,7 @@ package seccomp
|
|||||||
#include "seccomp-build.h"
|
#include "seccomp-build.h"
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -56,29 +57,29 @@ var resPrefix = [...]string{
|
|||||||
7: "seccomp_load failed",
|
7: "seccomp_load failed",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyscallOpts = C.f_syscall_opts
|
type FilterOpts = C.f_filter_opts
|
||||||
|
|
||||||
const (
|
const (
|
||||||
flagVerbose SyscallOpts = C.F_VERBOSE
|
filterVerbose FilterOpts = C.F_VERBOSE
|
||||||
// FlagExt are project-specific extensions.
|
// FilterExt are project-specific extensions.
|
||||||
FlagExt SyscallOpts = C.F_EXT
|
FilterExt FilterOpts = C.F_EXT
|
||||||
// FlagDenyNS denies namespace setup syscalls.
|
// FilterDenyNS denies namespace setup syscalls.
|
||||||
FlagDenyNS SyscallOpts = C.F_DENY_NS
|
FilterDenyNS FilterOpts = C.F_DENY_NS
|
||||||
// FlagDenyTTY denies faking input.
|
// FilterDenyTTY denies faking input.
|
||||||
FlagDenyTTY SyscallOpts = C.F_DENY_TTY
|
FilterDenyTTY FilterOpts = C.F_DENY_TTY
|
||||||
// FlagDenyDevel denies development-related syscalls.
|
// FilterDenyDevel denies development-related syscalls.
|
||||||
FlagDenyDevel SyscallOpts = C.F_DENY_DEVEL
|
FilterDenyDevel FilterOpts = C.F_DENY_DEVEL
|
||||||
// FlagMultiarch allows multiarch/emulation.
|
// FilterMultiarch allows multiarch/emulation.
|
||||||
FlagMultiarch SyscallOpts = C.F_MULTIARCH
|
FilterMultiarch FilterOpts = C.F_MULTIARCH
|
||||||
// FlagLinux32 sets PER_LINUX32.
|
// FilterLinux32 sets PER_LINUX32.
|
||||||
FlagLinux32 SyscallOpts = C.F_LINUX32
|
FilterLinux32 FilterOpts = C.F_LINUX32
|
||||||
// FlagCan allows AF_CAN.
|
// FilterCan allows AF_CAN.
|
||||||
FlagCan SyscallOpts = C.F_CAN
|
FilterCan FilterOpts = C.F_CAN
|
||||||
// FlagBluetooth allows AF_BLUETOOTH.
|
// FilterBluetooth allows AF_BLUETOOTH.
|
||||||
FlagBluetooth SyscallOpts = C.F_BLUETOOTH
|
FilterBluetooth FilterOpts = C.F_BLUETOOTH
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildFilter(fd int, opts SyscallOpts) error {
|
func buildFilter(fd int, opts FilterOpts) error {
|
||||||
var (
|
var (
|
||||||
arch C.uint32_t = 0
|
arch C.uint32_t = 0
|
||||||
multiarch C.uint32_t = 0
|
multiarch C.uint32_t = 0
|
||||||
@ -99,7 +100,7 @@ func buildFilter(fd int, opts SyscallOpts) error {
|
|||||||
// this removes repeated transitions between C and Go execution
|
// this removes repeated transitions between C and Go execution
|
||||||
// when producing log output via F_println and CPrintln is nil
|
// when producing log output via F_println and CPrintln is nil
|
||||||
if fp := printlnP.Load(); fp != nil {
|
if fp := printlnP.Load(); fp != nil {
|
||||||
opts |= flagVerbose
|
opts |= filterVerbose
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret C.int
|
var ret C.int
|
||||||
|
@ -37,7 +37,7 @@ let
|
|||||||
{
|
{
|
||||||
name = "check-sandbox-${tc.name}";
|
name = "check-sandbox-${tc.name}";
|
||||||
verbose = true;
|
verbose = true;
|
||||||
inherit (tc) tty mapRealUid;
|
inherit (tc) tty device mapRealUid;
|
||||||
share = testProgram;
|
share = testProgram;
|
||||||
packages = [ ];
|
packages = [ ];
|
||||||
path = "${testProgram}/bin/fortify-test";
|
path = "${testProgram}/bin/fortify-test";
|
||||||
@ -51,4 +51,5 @@ in
|
|||||||
preset = callTestCase ./preset.nix;
|
preset = callTestCase ./preset.nix;
|
||||||
tty = callTestCase ./tty.nix;
|
tty = callTestCase ./tty.nix;
|
||||||
mapuid = callTestCase ./mapuid.nix;
|
mapuid = callTestCase ./mapuid.nix;
|
||||||
|
device = callTestCase ./device.nix;
|
||||||
}
|
}
|
||||||
|
203
test/sandbox/case/device.nix
Normal file
203
test/sandbox/case/device.nix
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
fs,
|
||||||
|
ent,
|
||||||
|
ignore,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
name = "device";
|
||||||
|
tty = false;
|
||||||
|
device = true;
|
||||||
|
mapRealUid = false;
|
||||||
|
|
||||||
|
want = {
|
||||||
|
env = [
|
||||||
|
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus"
|
||||||
|
"HOME=/var/lib/fortify/u0/a4"
|
||||||
|
"PULSE_SERVER=unix:/run/user/65534/pulse/native"
|
||||||
|
"SHELL=/run/current-system/sw/bin/bash"
|
||||||
|
"TERM=linux"
|
||||||
|
"USER=u0_a4"
|
||||||
|
"WAYLAND_DISPLAY=wayland-0"
|
||||||
|
"XDG_RUNTIME_DIR=/run/user/65534"
|
||||||
|
"XDG_SESSION_CLASS=user"
|
||||||
|
"XDG_SESSION_TYPE=tty"
|
||||||
|
];
|
||||||
|
|
||||||
|
fs = fs "dead" {
|
||||||
|
".fortify" = fs "800001ed" { } null;
|
||||||
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
|
dev = fs "800001ed" null null;
|
||||||
|
etc = fs "800001ed" {
|
||||||
|
".clean" = fs "80001ff" null null;
|
||||||
|
".host" = fs "800001c0" null null;
|
||||||
|
".updated" = fs "80001ff" null null;
|
||||||
|
"NIXOS" = fs "80001ff" null null;
|
||||||
|
"X11" = fs "80001ff" null null;
|
||||||
|
"alsa" = fs "80001ff" null null;
|
||||||
|
"bashrc" = fs "80001ff" null null;
|
||||||
|
"binfmt.d" = fs "80001ff" null null;
|
||||||
|
"dbus-1" = fs "80001ff" null null;
|
||||||
|
"default" = fs "80001ff" null null;
|
||||||
|
"dhcpcd.exit-hook" = fs "80001ff" null null;
|
||||||
|
"fonts" = fs "80001ff" null null;
|
||||||
|
"fstab" = fs "80001ff" null null;
|
||||||
|
"fsurc" = fs "80001ff" null null;
|
||||||
|
"fuse.conf" = fs "80001ff" null null;
|
||||||
|
"group" = fs "180" null "fortify:x:65534:\n";
|
||||||
|
"host.conf" = fs "80001ff" null null;
|
||||||
|
"hostname" = fs "80001ff" null null;
|
||||||
|
"hosts" = fs "80001ff" null null;
|
||||||
|
"inputrc" = fs "80001ff" null null;
|
||||||
|
"issue" = fs "80001ff" null null;
|
||||||
|
"kbd" = fs "80001ff" null null;
|
||||||
|
"locale.conf" = fs "80001ff" null null;
|
||||||
|
"login.defs" = fs "80001ff" null null;
|
||||||
|
"lsb-release" = fs "80001ff" null null;
|
||||||
|
"lvm" = fs "80001ff" null null;
|
||||||
|
"machine-id" = fs "80001ff" null null;
|
||||||
|
"man_db.conf" = fs "80001ff" null null;
|
||||||
|
"modprobe.d" = fs "80001ff" null null;
|
||||||
|
"modules-load.d" = fs "80001ff" null null;
|
||||||
|
"mtab" = fs "80001ff" null null;
|
||||||
|
"nanorc" = fs "80001ff" null null;
|
||||||
|
"netgroup" = fs "80001ff" null null;
|
||||||
|
"nix" = fs "80001ff" null null;
|
||||||
|
"nixos" = fs "80001ff" null null;
|
||||||
|
"nscd.conf" = fs "80001ff" null null;
|
||||||
|
"nsswitch.conf" = fs "80001ff" null null;
|
||||||
|
"os-release" = fs "80001ff" null null;
|
||||||
|
"pam" = fs "80001ff" null null;
|
||||||
|
"pam.d" = fs "80001ff" null null;
|
||||||
|
"passwd" = fs "180" null "u0_a4:x:65534:65534:Fortify:/var/lib/fortify/u0/a4:/run/current-system/sw/bin/bash\n";
|
||||||
|
"pipewire" = fs "80001ff" null null;
|
||||||
|
"pki" = fs "80001ff" null null;
|
||||||
|
"polkit-1" = fs "80001ff" null null;
|
||||||
|
"profile" = fs "80001ff" null null;
|
||||||
|
"protocols" = fs "80001ff" null null;
|
||||||
|
"resolv.conf" = fs "80001ff" null null;
|
||||||
|
"resolvconf.conf" = fs "80001ff" null null;
|
||||||
|
"rpc" = fs "80001ff" null null;
|
||||||
|
"services" = fs "80001ff" null null;
|
||||||
|
"set-environment" = fs "80001ff" null null;
|
||||||
|
"shadow" = fs "80001ff" null null;
|
||||||
|
"shells" = fs "80001ff" null null;
|
||||||
|
"ssh" = fs "80001ff" null null;
|
||||||
|
"ssl" = fs "80001ff" null null;
|
||||||
|
"static" = fs "80001ff" null null;
|
||||||
|
"subgid" = fs "80001ff" null null;
|
||||||
|
"subuid" = fs "80001ff" null null;
|
||||||
|
"sudoers" = fs "80001ff" null null;
|
||||||
|
"sway" = fs "80001ff" null null;
|
||||||
|
"sysctl.d" = fs "80001ff" null null;
|
||||||
|
"systemd" = fs "80001ff" null null;
|
||||||
|
"terminfo" = fs "80001ff" null null;
|
||||||
|
"tmpfiles.d" = fs "80001ff" null null;
|
||||||
|
"udev" = fs "80001ff" null null;
|
||||||
|
"vconsole.conf" = fs "80001ff" null null;
|
||||||
|
"xdg" = fs "80001ff" null null;
|
||||||
|
"zoneinfo" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
nix = fs "800001c0" { store = fs "801001fd" null null; } null;
|
||||||
|
proc = fs "8000016d" null null;
|
||||||
|
run = fs "800001ed" {
|
||||||
|
current-system = fs "80001ff" null null;
|
||||||
|
opengl-driver = fs "80001ff" null null;
|
||||||
|
user = fs "800001ed" {
|
||||||
|
"65534" = fs "800001c0" {
|
||||||
|
bus = fs "10001fd" null null;
|
||||||
|
pulse = fs "800001c0" { native = fs "10001b6" null null; } null;
|
||||||
|
wayland-0 = fs "1000038" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
sys = fs "800001c0" {
|
||||||
|
block = fs "800001ed" {
|
||||||
|
fd0 = fs "80001ff" null null;
|
||||||
|
loop0 = fs "80001ff" null null;
|
||||||
|
loop1 = fs "80001ff" null null;
|
||||||
|
loop2 = fs "80001ff" null null;
|
||||||
|
loop3 = fs "80001ff" null null;
|
||||||
|
loop4 = fs "80001ff" null null;
|
||||||
|
loop5 = fs "80001ff" null null;
|
||||||
|
loop6 = fs "80001ff" null null;
|
||||||
|
loop7 = fs "80001ff" null null;
|
||||||
|
sr0 = fs "80001ff" null null;
|
||||||
|
vda = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
bus = fs "800001ed" null null;
|
||||||
|
class = fs "800001ed" null null;
|
||||||
|
dev = fs "800001ed" {
|
||||||
|
block = fs "800001ed" null null;
|
||||||
|
char = fs "800001ed" null null;
|
||||||
|
} null;
|
||||||
|
devices = fs "800001ed" null null;
|
||||||
|
} null;
|
||||||
|
tmp = fs "800001f8" { } null;
|
||||||
|
usr = fs "800001c0" { bin = fs "800001ed" { env = fs "80001ff" null null; } null; } null;
|
||||||
|
var = fs "800001c0" {
|
||||||
|
lib = fs "800001c0" {
|
||||||
|
fortify = fs "800001c0" {
|
||||||
|
u0 = fs "800001c0" {
|
||||||
|
a4 = fs "800001c0" {
|
||||||
|
".cache" = fs "800001ed" { ".keep" = fs "80001ff" null ""; } null;
|
||||||
|
".config" = fs "800001ed" { "environment.d" = fs "800001ed" { "10-home-manager.conf" = fs "80001ff" null null; } null; } null;
|
||||||
|
".local" = fs "800001ed" {
|
||||||
|
state = fs "800001ed" {
|
||||||
|
home-manager = fs "800001ed" { gcroots = fs "800001ed" { current-home = fs "80001ff" null null; } null; } null;
|
||||||
|
nix = fs "800001ed" {
|
||||||
|
profiles = fs "800001ed" {
|
||||||
|
home-manager = fs "80001ff" null null;
|
||||||
|
home-manager-1-link = fs "80001ff" null null;
|
||||||
|
profile = fs "80001ff" null null;
|
||||||
|
profile-1-link = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
".nix-defexpr" = fs "800001ed" {
|
||||||
|
channels = fs "80001ff" null null;
|
||||||
|
channels_root = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
".nix-profile" = fs "80001ff" null null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
run = fs "800001ed" { nscd = fs "800001ed" { } null; } null;
|
||||||
|
} null;
|
||||||
|
} null;
|
||||||
|
|
||||||
|
mount = [
|
||||||
|
(ent "/sysroot" "/" "rw,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent "/" "/proc" "rw,nosuid,nodev,noexec,relatime" "proc" "proc" "rw")
|
||||||
|
(ent "/" "/.fortify" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||||
|
(ent "/" "/dev" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/" "/dev/pts" "rw,nosuid,noexec,relatime" "devpts" "devpts" "rw,gid=3,mode=620,ptmxmode=666")
|
||||||
|
(ent "/" "/dev/shm" "rw,nosuid,nodev" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent "/" ignore ignore ignore ignore ignore) # order not deterministic
|
||||||
|
(ent "/" ignore ignore ignore ignore ignore)
|
||||||
|
(ent "/bin" "/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/usr/bin" "/usr/bin" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/nix/store" "ro,nosuid,nodev,relatime" "overlay" "overlay" "rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on")
|
||||||
|
(ent "/block" "/sys/block" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/bus" "/sys/bus" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/class" "/sys/class" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
|
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000004,gid=1000004")
|
||||||
|
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000004,gid=1000004")
|
||||||
|
(ent "/tmp/fortify.1000/tmpdir/4" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/var/lib/fortify/u0/a4" "/var/lib/fortify/u0/a4" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/etc/passwd" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent ignore "/etc/group" "ro,nosuid,nodev,relatime" "tmpfs" "rootfs" "rw,uid=1000004,gid=1000004")
|
||||||
|
(ent ignore "/run/user/65534/wayland-0" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent ignore "/run/user/65534/pulse/native" "ro,nosuid,nodev,relatime" "tmpfs" "tmpfs" ignore)
|
||||||
|
(ent ignore "/run/user/65534/bus" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
(ent "/" "/var/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8k,mode=755,uid=1000004,gid=1000004")
|
||||||
|
];
|
||||||
|
|
||||||
|
seccomp = true;
|
||||||
|
};
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
{
|
{
|
||||||
name = "mapuid";
|
name = "mapuid";
|
||||||
tty = false;
|
tty = false;
|
||||||
|
device = false;
|
||||||
mapRealUid = true;
|
mapRealUid = true;
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
@ -23,9 +24,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
".fortify" = fs "800001ed" {
|
".fortify" = fs "800001ed" { } null;
|
||||||
etc = fs "800001ed" null null;
|
|
||||||
} null;
|
|
||||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
core = fs "80001ff" null null;
|
core = fs "80001ff" null null;
|
||||||
@ -54,6 +53,7 @@
|
|||||||
} null;
|
} null;
|
||||||
etc = fs "800001ed" {
|
etc = fs "800001ed" {
|
||||||
".clean" = fs "80001ff" null null;
|
".clean" = fs "80001ff" null null;
|
||||||
|
".host" = fs "800001c0" null null;
|
||||||
".updated" = fs "80001ff" null null;
|
".updated" = fs "80001ff" null null;
|
||||||
"NIXOS" = fs "80001ff" null null;
|
"NIXOS" = fs "80001ff" null null;
|
||||||
"X11" = fs "80001ff" null null;
|
"X11" = fs "80001ff" null null;
|
||||||
@ -213,7 +213,7 @@
|
|||||||
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000003,gid=1000003")
|
||||||
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000003,gid=1000003")
|
(ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000003,gid=1000003")
|
||||||
(ent "/tmp/fortify.1000/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/fortify.1000/tmpdir/3" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
{
|
{
|
||||||
name = "preset";
|
name = "preset";
|
||||||
tty = false;
|
tty = false;
|
||||||
|
device = false;
|
||||||
mapRealUid = false;
|
mapRealUid = false;
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
@ -23,9 +24,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
".fortify" = fs "800001ed" {
|
".fortify" = fs "800001ed" { } null;
|
||||||
etc = fs "800001ed" null null;
|
|
||||||
} null;
|
|
||||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
core = fs "80001ff" null null;
|
core = fs "80001ff" null null;
|
||||||
@ -54,6 +53,7 @@
|
|||||||
} null;
|
} null;
|
||||||
etc = fs "800001ed" {
|
etc = fs "800001ed" {
|
||||||
".clean" = fs "80001ff" null null;
|
".clean" = fs "80001ff" null null;
|
||||||
|
".host" = fs "800001c0" null null;
|
||||||
".updated" = fs "80001ff" null null;
|
".updated" = fs "80001ff" null null;
|
||||||
"NIXOS" = fs "80001ff" null null;
|
"NIXOS" = fs "80001ff" null null;
|
||||||
"X11" = fs "80001ff" null null;
|
"X11" = fs "80001ff" null null;
|
||||||
@ -213,7 +213,7 @@
|
|||||||
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000001,gid=1000001")
|
||||||
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000001,gid=1000001")
|
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000001,gid=1000001")
|
||||||
(ent "/tmp/fortify.1000/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/fortify.1000/tmpdir/1" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
{
|
{
|
||||||
name = "tty";
|
name = "tty";
|
||||||
tty = true;
|
tty = true;
|
||||||
|
device = false;
|
||||||
mapRealUid = false;
|
mapRealUid = false;
|
||||||
|
|
||||||
want = {
|
want = {
|
||||||
@ -23,9 +24,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
fs = fs "dead" {
|
fs = fs "dead" {
|
||||||
".fortify" = fs "800001ed" {
|
".fortify" = fs "800001ed" { } null;
|
||||||
etc = fs "800001ed" null null;
|
|
||||||
} null;
|
|
||||||
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
bin = fs "800001ed" { sh = fs "80001ff" null null; } null;
|
||||||
dev = fs "800001ed" {
|
dev = fs "800001ed" {
|
||||||
console = fs "4200190" null null;
|
console = fs "4200190" null null;
|
||||||
@ -55,6 +54,7 @@
|
|||||||
} null;
|
} null;
|
||||||
etc = fs "800001ed" {
|
etc = fs "800001ed" {
|
||||||
".clean" = fs "80001ff" null null;
|
".clean" = fs "80001ff" null null;
|
||||||
|
".host" = fs "800001c0" null null;
|
||||||
".updated" = fs "80001ff" null null;
|
".updated" = fs "80001ff" null null;
|
||||||
"NIXOS" = fs "80001ff" null null;
|
"NIXOS" = fs "80001ff" null null;
|
||||||
"X11" = fs "80001ff" null null;
|
"X11" = fs "80001ff" null null;
|
||||||
@ -215,7 +215,7 @@
|
|||||||
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/dev" "/sys/dev" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
(ent "/devices" "/sys/devices" "ro,nosuid,nodev,noexec,relatime" "sysfs" "sysfs" "rw")
|
||||||
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
(ent "/dri" "/dev/dri" "rw,nosuid" "devtmpfs" "devtmpfs" ignore)
|
||||||
(ent "/etc" "/.fortify/etc" "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
(ent "/" "/run/user" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=4k,mode=755,uid=1000002,gid=1000002")
|
||||||
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000002,gid=1000002")
|
(ent "/" "/run/user/65534" "rw,nosuid,nodev,relatime" "tmpfs" "tmpfs" "rw,size=8192k,mode=700,uid=1000002,gid=1000002")
|
||||||
(ent "/tmp/fortify.1000/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
(ent "/tmp/fortify.1000/tmpdir/2" "/tmp" "rw,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw")
|
||||||
|
@ -67,10 +67,11 @@ in
|
|||||||
|
|
||||||
home-manager = _: _: { home.stateVersion = "23.05"; };
|
home-manager = _: _: { home.stateVersion = "23.05"; };
|
||||||
|
|
||||||
apps = [
|
apps = with testCases; [
|
||||||
testCases.preset
|
preset
|
||||||
testCases.tty
|
tty
|
||||||
testCases.mapuid
|
mapuid
|
||||||
|
device
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ def check_sandbox(name):
|
|||||||
check_sandbox("preset")
|
check_sandbox("preset")
|
||||||
check_sandbox("tty")
|
check_sandbox("tty")
|
||||||
check_sandbox("mapuid")
|
check_sandbox("mapuid")
|
||||||
|
check_sandbox("device")
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
# Exit Sway and verify process exit status 0:
|
||||||
swaymsg("exit", succeed=False)
|
swaymsg("exit", succeed=False)
|
||||||
|
50
test/test.py
50
test/test.py
@ -69,8 +69,8 @@ def check_state(name, enablements):
|
|||||||
if len(config['args']) != 1 or config['args'][0] != command:
|
if len(config['args']) != 1 or config['args'][0] != command:
|
||||||
raise Exception(f"unexpected args {config['args']}")
|
raise Exception(f"unexpected args {config['args']}")
|
||||||
|
|
||||||
if config['confinement']['enablements'] != enablements:
|
if config['enablements'] != enablements:
|
||||||
raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")
|
raise Exception(f"unexpected enablements {instance['config']['enablements']}")
|
||||||
|
|
||||||
|
|
||||||
def fortify(command):
|
def fortify(command):
|
||||||
@ -169,6 +169,16 @@ machine.send_chars("exit\n")
|
|||||||
machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
|
machine.wait_for_file("/tmp/p0-exit-ok", timeout=15)
|
||||||
machine.fail("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000000")
|
machine.fail("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000000")
|
||||||
|
|
||||||
|
# Check interrupt shim behaviour:
|
||||||
|
swaymsg("exec sh -c 'ne-foot; echo -n $? > /tmp/monitor-exit-code'")
|
||||||
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
machine.succeed("pkill -INT -f 'fortify -v app '")
|
||||||
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
|
if interrupt_exit_code != 254:
|
||||||
|
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement:
|
# Start app (foot) with Wayland enablement:
|
||||||
swaymsg("exec ne-foot")
|
swaymsg("exec ne-foot")
|
||||||
wait_for_window(f"u0_a{aid(0)}@machine")
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
@ -176,25 +186,11 @@ machine.send_chars("clear; wayland-info && touch /tmp/client-ok\n")
|
|||||||
machine.wait_for_file(tmpdir_path(0, "client-ok"), timeout=15)
|
machine.wait_for_file(tmpdir_path(0, "client-ok"), timeout=15)
|
||||||
collect_state_ui("foot_wayland")
|
collect_state_ui("foot_wayland")
|
||||||
check_state("ne-foot", 1)
|
check_state("ne-foot", 1)
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
# Verify lack of acl on XDG_RUNTIME_DIR:
|
||||||
print(machine.succeed(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {aid(0) + 1000000}"))
|
machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {aid(0) + 1000000}")
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
machine.fail(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {aid(0) + 1000000}", timeout=5)
|
||||||
machine.wait_until_fails(f"getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep {aid(0) + 1000000}", timeout=5)
|
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement from a terminal:
|
|
||||||
swaymsg("exec foot $SHELL -c '(ne-foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'")
|
|
||||||
wait_for_window(f"u0_a{aid(0)}@machine")
|
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/term-ok\n")
|
|
||||||
machine.wait_for_file(tmpdir_path(0, "term-ok"), timeout=15)
|
|
||||||
machine.wait_for_file("/tmp/ps-show-ok", timeout=5)
|
|
||||||
collect_state_ui("foot_wayland_term")
|
|
||||||
check_state("ne-foot", 1)
|
|
||||||
machine.send_chars("exit\n")
|
|
||||||
wait_for_window("foot")
|
|
||||||
machine.send_key("ctrl-c")
|
|
||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
|
||||||
|
|
||||||
# Test PulseAudio (fortify does not support PipeWire yet):
|
# Test PulseAudio (fortify does not support PipeWire yet):
|
||||||
swaymsg("exec pa-foot")
|
swaymsg("exec pa-foot")
|
||||||
@ -233,6 +229,22 @@ machine.wait_until_fails(f"getfacl --absolute-names --omit-header --numeric /run
|
|||||||
# Test syscall filter:
|
# Test syscall filter:
|
||||||
print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure"))
|
print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure"))
|
||||||
|
|
||||||
|
# Start app (foot) with Wayland enablement from a terminal:
|
||||||
|
swaymsg("exec foot $SHELL -c '(ne-foot) & disown && exec $SHELL'")
|
||||||
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
machine.send_chars("clear; wayland-info && touch /tmp/term-ok\n")
|
||||||
|
machine.wait_for_file(tmpdir_path(0, "term-ok"), timeout=15)
|
||||||
|
machine.send_key("alt-h")
|
||||||
|
machine.send_chars("clear; fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && exec cat\n")
|
||||||
|
machine.wait_for_file("/tmp/ps-show-ok", timeout=5)
|
||||||
|
collect_state_ui("foot_wayland_term")
|
||||||
|
check_state("ne-foot", 1)
|
||||||
|
machine.send_key("alt-l")
|
||||||
|
machine.send_chars("exit\n")
|
||||||
|
wait_for_window("alice@machine")
|
||||||
|
machine.send_key("ctrl-c")
|
||||||
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
# Exit Sway and verify process exit status 0:
|
||||||
swaymsg("exit", succeed=False)
|
swaymsg("exit", succeed=False)
|
||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
machine.wait_for_file("/tmp/sway-exit-ok")
|
||||||
|
Loading…
Reference in New Issue
Block a user