Compare commits
23 Commits
820f48ef94
...
90b86a5531
Author | SHA1 | Date | |
---|---|---|---|
90b86a5531 | |||
f545e154f0 | |||
268a90f1a5 | |||
3054527ca5 | |||
ddb2f9c11b | |||
6ae02e72fa | |||
989fb5395f | |||
f955b15b84 | |||
0340c67995 | |||
72b0160aad | |||
ea8d1c07df | |||
a0062d8275 | |||
43d2e4f5d7 | |||
be7d944b39 | |||
ace97952cc | |||
73146ea7fa | |||
88040504b2 | |||
1fd571d561 | |||
be30e2f11e | |||
aaebb8f3ab | |||
1f74b636d3 | |||
e431ab3c24 | |||
3fba33687b |
@ -8,39 +8,11 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Create release
|
name: Create release
|
||||||
runs-on: ubuntu-latest
|
runs-on: nix
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: '>=1.23.0'
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
# explicitly enable sandbox
|
|
||||||
install_options: --daemon
|
|
||||||
extra_nix_config: |
|
|
||||||
sandbox = true
|
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
|
||||||
enable_kvm: true
|
|
||||||
|
|
||||||
- name: Ensure environment
|
|
||||||
run: >-
|
|
||||||
apt-get update && apt-get install -y sqlite3
|
|
||||||
if: ${{ runner.os == 'Linux' }}
|
|
||||||
|
|
||||||
- name: Restore Nix store
|
|
||||||
uses: nix-community/cache-nix-action@v5
|
|
||||||
with:
|
|
||||||
primary-key: build-dist-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
|
|
||||||
restore-prefixes-first-match: build-dist-${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Build for release
|
- name: Build for release
|
||||||
run: nix build --print-out-paths --print-build-logs .#dist
|
run: nix build --print-out-paths --print-build-logs .#dist
|
||||||
|
|
||||||
|
@ -7,39 +7,11 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run NixOS test
|
name: Run NixOS test
|
||||||
runs-on: ubuntu-latest
|
runs-on: nix
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
# explicitly enable sandbox
|
|
||||||
install_options: --daemon
|
|
||||||
extra_nix_config: |
|
|
||||||
sandbox = true
|
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
|
||||||
enable_kvm: true
|
|
||||||
|
|
||||||
- name: Ensure environment
|
|
||||||
run: >-
|
|
||||||
apt-get update && apt-get install -y sqlite3
|
|
||||||
if: ${{ runner.os == 'Linux' }}
|
|
||||||
|
|
||||||
- name: Restore Nix store
|
|
||||||
uses: nix-community/cache-nix-action@v5
|
|
||||||
with:
|
|
||||||
primary-key: flake-check-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
|
|
||||||
restore-prefixes-first-match: flake-check-${{ runner.os }}-
|
|
||||||
gc-max-store-size-linux: 1073741824
|
|
||||||
purge: true
|
|
||||||
purge-prefixes: flake-check-${{ runner.os }}-
|
|
||||||
purge-created: 60
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
nix --print-build-logs --experimental-features 'nix-command flakes' flake check
|
nix --print-build-logs --experimental-features 'nix-command flakes' flake check
|
||||||
@ -54,39 +26,11 @@ jobs:
|
|||||||
|
|
||||||
dist:
|
dist:
|
||||||
name: Create distribution
|
name: Create distribution
|
||||||
runs-on: ubuntu-latest
|
runs-on: nix
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@v30
|
|
||||||
with:
|
|
||||||
# explicitly enable sandbox
|
|
||||||
install_options: --daemon
|
|
||||||
extra_nix_config: |
|
|
||||||
sandbox = true
|
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
|
||||||
enable_kvm: true
|
|
||||||
|
|
||||||
- name: Ensure environment
|
|
||||||
run: >-
|
|
||||||
apt-get update && apt-get install -y sqlite3
|
|
||||||
if: ${{ runner.os == 'Linux' }}
|
|
||||||
|
|
||||||
- name: Restore Nix store
|
|
||||||
uses: nix-community/cache-nix-action@v5
|
|
||||||
with:
|
|
||||||
primary-key: build-dist-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
|
|
||||||
restore-prefixes-first-match: build-dist-${{ runner.os }}-
|
|
||||||
gc-max-store-size-linux: 1073741824
|
|
||||||
purge: true
|
|
||||||
purge-prefixes: build-dist-${{ runner.os }}-
|
|
||||||
purge-created: 60
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- name: Build for test
|
- name: Build for test
|
||||||
id: build-test
|
id: build-test
|
||||||
run: >-
|
run: >-
|
||||||
|
@ -61,8 +61,19 @@ func main() {
|
|||||||
// aid
|
// aid
|
||||||
uid := 1000000
|
uid := 1000000
|
||||||
|
|
||||||
|
// refuse to run if fsurc is not protected correctly
|
||||||
|
if s, err := os.Stat(fsuConfFile); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else if s.Mode().Perm() != 0400 {
|
||||||
|
log.Fatal("bad fsurc perm")
|
||||||
|
} else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
|
||||||
|
log.Fatal("fsurc must be owned by uid 0")
|
||||||
|
}
|
||||||
|
|
||||||
// authenticate before accepting user input
|
// authenticate before accepting user input
|
||||||
if fid, ok := parseConfig(fsuConfFile, puid); !ok {
|
if f, err := os.Open(fsuConfFile); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
} else if fid, ok := mustParseConfig(f, puid); !ok {
|
||||||
log.Fatalf("uid %d is not in the fsurc file", puid)
|
log.Fatalf("uid %d is not in the fsurc file", puid)
|
||||||
} else {
|
} else {
|
||||||
uid += fid * 10000
|
uid += fid * 10000
|
||||||
|
@ -4,10 +4,9 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseUint32Fast(s string) (int, error) {
|
func parseUint32Fast(s string) (int, error) {
|
||||||
@ -23,55 +22,46 @@ func parseUint32Fast(s string) (int, error) {
|
|||||||
for i, ch := range []byte(s) {
|
for i, ch := range []byte(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, fmt.Errorf("invalid character '%s' at index %d", string([]byte{ch}), i)
|
return -1, fmt.Errorf("invalid character '%s' at index %d", string(ch+'0'), i)
|
||||||
}
|
}
|
||||||
n = n*10 + int(ch)
|
n = n*10 + int(ch)
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(p string, puid int) (fid int, ok bool) {
|
func parseConfig(r io.Reader, puid int) (fid int, ok bool, err error) {
|
||||||
// refuse to run if fsurc is not protected correctly
|
s := bufio.NewScanner(r)
|
||||||
if s, err := os.Stat(p); err != nil {
|
var line, puid0 int
|
||||||
log.Fatal(err)
|
for s.Scan() {
|
||||||
} else if s.Mode().Perm() != 0400 {
|
line++
|
||||||
log.Fatal("bad fsurc perm")
|
|
||||||
} else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
|
|
||||||
log.Fatal("fsurc must be owned by uid 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r, err := os.Open(p); err != nil {
|
// <puid> <fid>
|
||||||
log.Fatal(err)
|
lf := strings.SplitN(s.Text(), " ", 2)
|
||||||
return -1, false
|
if len(lf) != 2 {
|
||||||
} else {
|
return -1, false, fmt.Errorf("invalid entry on line %d", line)
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
var line int
|
|
||||||
for s.Scan() {
|
|
||||||
line++
|
|
||||||
|
|
||||||
// <puid> <fid>
|
|
||||||
lf := strings.SplitN(s.Text(), " ", 2)
|
|
||||||
if len(lf) != 2 {
|
|
||||||
log.Fatalf("invalid entry on line %d", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
var puid0 int
|
|
||||||
if puid0, err = parseUint32Fast(lf[0]); err != nil || puid0 < 1 {
|
|
||||||
log.Fatalf("invalid parent uid on line %d", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = puid0 == puid
|
|
||||||
if ok {
|
|
||||||
// allowed fid range 0 to 99
|
|
||||||
if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 {
|
|
||||||
log.Fatalf("invalid fortify uid on line %d", line)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = s.Err(); err != nil {
|
|
||||||
log.Fatalf("cannot read fsurc: %v", err)
|
puid0, err = parseUint32Fast(lf[0])
|
||||||
|
if err != nil || puid0 < 1 {
|
||||||
|
return -1, false, fmt.Errorf("invalid parent uid on line %d", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = puid0 == puid
|
||||||
|
if ok {
|
||||||
|
// allowed fid range 0 to 99
|
||||||
|
if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 {
|
||||||
|
return -1, false, fmt.Errorf("invalid fortify uid on line %d", line)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return -1, false
|
|
||||||
}
|
}
|
||||||
|
return -1, false, s.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseConfig(r io.Reader, puid int) (int, bool) {
|
||||||
|
fid, ok, err := parseConfig(r, puid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return fid, ok
|
||||||
}
|
}
|
||||||
|
96
cmd/fsu/parse_test.go
Normal file
96
cmd/fsu/parse_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseUint32Fast(t *testing.T) {
|
||||||
|
t.Run("zero-length", func(t *testing.T) {
|
||||||
|
if _, err := parseUint32Fast(""); err == nil || err.Error() != "zero length string" {
|
||||||
|
t.Errorf(`parseUint32Fast(""): error = %v`, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("overflow", func(t *testing.T) {
|
||||||
|
if _, err := parseUint32Fast("10000000000"); err == nil || err.Error() != "string too long" {
|
||||||
|
t.Errorf("parseUint32Fast: error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("invalid byte", func(t *testing.T) {
|
||||||
|
if _, err := parseUint32Fast("meow"); err == nil || err.Error() != "invalid character 'm' at index 0" {
|
||||||
|
t.Errorf(`parseUint32Fast("meow"): error = %v`, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("full range", func(t *testing.T) {
|
||||||
|
testRange := func(i, end int) {
|
||||||
|
for ; i < end; i++ {
|
||||||
|
s := strconv.Itoa(i)
|
||||||
|
w := i
|
||||||
|
t.Run("parse "+s, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
v, err := parseUint32Fast(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("parseUint32Fast(%q): error = %v",
|
||||||
|
s, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v != w {
|
||||||
|
t.Errorf("parseUint32Fast(%q): got %v",
|
||||||
|
s, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testRange(0, 5000)
|
||||||
|
testRange(105000, 110000)
|
||||||
|
testRange(23005000, 23010000)
|
||||||
|
testRange(456005000, 456010000)
|
||||||
|
testRange(7890005000, 7890010000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
puid, want int
|
||||||
|
wantErr string
|
||||||
|
rc string
|
||||||
|
}{
|
||||||
|
{"empty", 0, -1, "", ``},
|
||||||
|
{"invalid field", 0, -1, "invalid entry on line 1", `9`},
|
||||||
|
{"invalid puid", 0, -1, "invalid parent uid on line 1", `f 9`},
|
||||||
|
{"invalid fid", 1000, -1, "invalid fortify uid on line 1", `1000 f`},
|
||||||
|
{"match", 1000, 0, "", `1000 0`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fid, ok, err := parseConfig(bytes.NewBufferString(tc.rc), tc.puid)
|
||||||
|
if err == nil && tc.wantErr != "" {
|
||||||
|
t.Errorf("parseConfig: error = %v; wantErr %q",
|
||||||
|
err, tc.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil && err.Error() != tc.wantErr {
|
||||||
|
t.Errorf("parseConfig: error = %q; wantErr %q",
|
||||||
|
err, tc.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok == (tc.want == -1) {
|
||||||
|
t.Errorf("parseConfig: ok = %v; want %v",
|
||||||
|
ok, tc.want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fid != tc.want {
|
||||||
|
t.Errorf("parseConfig: fid = %v; want %v",
|
||||||
|
fid, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -59,17 +59,6 @@ func (p *Proxy) String() string {
|
|||||||
return "(unsealed dbus proxy)"
|
return "(unsealed dbus proxy)"
|
||||||
}
|
}
|
||||||
|
|
||||||
// BwrapStatic builds static bwrap args. This omits any fd-dependant args.
|
|
||||||
func (p *Proxy) BwrapStatic() []string {
|
|
||||||
p.lock.RLock()
|
|
||||||
defer p.lock.RUnlock()
|
|
||||||
|
|
||||||
if p.bwrap == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.bwrap.Args()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seal seals the Proxy instance.
|
// Seal seals the Proxy instance.
|
||||||
func (p *Proxy) Seal(session, system *Config) error {
|
func (p *Proxy) Seal(session, system *Config) error {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
@ -75,9 +75,7 @@ func NewBwrap(
|
|||||||
b.name = name
|
b.name = name
|
||||||
b.helperCmd = newHelperCmd(b, BubblewrapName, wt, argF, extraFiles)
|
b.helperCmd = newHelperCmd(b, BubblewrapName, wt, argF, extraFiles)
|
||||||
|
|
||||||
args := conf.Args()
|
if v, err := NewCheckedArgs(conf.Args(syncFd, b.extraFiles, &b.files)); err != nil {
|
||||||
conf.FDArgs(syncFd, &args, b.extraFiles, &b.files)
|
|
||||||
if v, err := NewCheckedArgs(args); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
f := proc.NewWriterTo(v)
|
f := proc.NewWriterTo(v)
|
||||||
|
@ -23,42 +23,22 @@ type FDBuilder interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Args returns a slice of bwrap args corresponding to c.
|
// Args returns a slice of bwrap args corresponding to c.
|
||||||
func (c *Config) Args() (args []string) {
|
func (c *Config) Args(syncFd *os.File, extraFiles *proc.ExtraFilesPre, files *[]proc.File) (args []string) {
|
||||||
builders := []Builder{
|
builders := []Builder{
|
||||||
c.boolArgs(),
|
c.boolArgs(),
|
||||||
c.intArgs(),
|
c.intArgs(),
|
||||||
c.stringArgs(),
|
c.stringArgs(),
|
||||||
c.pairArgs(),
|
c.pairArgs(),
|
||||||
}
|
|
||||||
|
|
||||||
// copy FSBuilder slice to builder slice
|
|
||||||
fb := make([]Builder, len(c.Filesystem)+1)
|
|
||||||
for i, f := range c.Filesystem {
|
|
||||||
fb[i] = f
|
|
||||||
}
|
|
||||||
fb[len(fb)-1] = c.Chmod
|
|
||||||
builders = append(builders, fb...)
|
|
||||||
|
|
||||||
// accumulate arg count
|
|
||||||
argc := 0
|
|
||||||
for _, b := range builders {
|
|
||||||
argc += b.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
args = make([]string, 0, argc)
|
|
||||||
for _, b := range builders {
|
|
||||||
b.Append(&args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) FDArgs(syncFd *os.File, args *[]string, extraFiles *proc.ExtraFilesPre, files *[]proc.File) {
|
|
||||||
builders := []FDBuilder{
|
|
||||||
c.seccompArgs(),
|
c.seccompArgs(),
|
||||||
newFile(positionalArgs[SyncFd], syncFd),
|
newFile(SyncFd.String(), syncFd),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builders = slices.Grow(builders, len(c.Filesystem)+1)
|
||||||
|
for _, f := range c.Filesystem {
|
||||||
|
builders = append(builders, f)
|
||||||
|
}
|
||||||
|
builders = append(builders, c.Chmod)
|
||||||
|
|
||||||
argc := 0
|
argc := 0
|
||||||
fc := 0
|
fc := 0
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
@ -67,22 +47,26 @@ func (c *Config) FDArgs(syncFd *os.File, args *[]string, extraFiles *proc.ExtraF
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
argc += l
|
argc += l
|
||||||
fc++
|
|
||||||
|
|
||||||
proc.InitFile(b, extraFiles)
|
if f, ok := b.(FDBuilder); ok {
|
||||||
|
fc++
|
||||||
|
proc.InitFile(f, extraFiles)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fc++ // allocate extra slot for stat fd
|
fc++ // allocate extra slot for stat fd
|
||||||
*args = slices.Grow(*args, argc)
|
|
||||||
*files = slices.Grow(*files, fc)
|
|
||||||
|
|
||||||
|
args = make([]string, 0, argc)
|
||||||
|
*files = slices.Grow(*files, fc)
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if b.Len() < 1 {
|
if b.Len() < 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
b.Append(&args)
|
||||||
|
|
||||||
b.Append(args)
|
if f, ok := b.(FDBuilder); ok {
|
||||||
*files = append(*files, b)
|
*files = append(*files, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package bwrap
|
package bwrap
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Bind binds mount src on host to dest in sandbox.
|
Bind binds mount src on host to dest in sandbox.
|
||||||
@ -39,60 +41,83 @@ func (c *Config) Bind(src, dest string, opts ...bool) *Config {
|
|||||||
|
|
||||||
if dev {
|
if dev {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{DevBindTry.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{DevBindTry.String(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{DevBind.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{DevBind.String(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
} else if write {
|
} else if write {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{BindTry.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{BindTry.String(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{Bind.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{Bind.String(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
} else {
|
} else {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{ROBindTry.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{ROBindTry.String(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{ROBind.Unwrap(), src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{ROBind.String(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write copy from FD to destination DEST
|
||||||
|
// (--file FD DEST)
|
||||||
|
func (c *Config) Write(dest string, payload []byte) *Config {
|
||||||
|
c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: DataWrite})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CopyBind copy from FD to file which is readonly bind-mounted on DEST
|
||||||
|
(--ro-bind-data FD DEST)
|
||||||
|
|
||||||
|
CopyBind(dest, payload, true) copy from FD to file which is bind-mounted on DEST
|
||||||
|
(--bind-data FD DEST)
|
||||||
|
*/
|
||||||
|
func (c *Config) CopyBind(dest string, payload []byte, opts ...bool) *Config {
|
||||||
|
t := DataROBind
|
||||||
|
if len(opts) > 0 && opts[0] {
|
||||||
|
t = DataBind
|
||||||
|
}
|
||||||
|
c.Filesystem = append(c.Filesystem, &DataConfig{Dest: dest, Data: payload, Type: t})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// Dir create dir in sandbox
|
// Dir create dir in sandbox
|
||||||
// (--dir DEST)
|
// (--dir DEST)
|
||||||
func (c *Config) Dir(dest string) *Config {
|
func (c *Config) Dir(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{Dir.Unwrap(), dest})
|
c.Filesystem = append(c.Filesystem, &stringF{Dir.String(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemountRO remount path as readonly; does not recursively remount
|
// RemountRO remount path as readonly; does not recursively remount
|
||||||
// (--remount-ro DEST)
|
// (--remount-ro DEST)
|
||||||
func (c *Config) RemountRO(dest string) *Config {
|
func (c *Config) RemountRO(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{RemountRO.Unwrap(), dest})
|
c.Filesystem = append(c.Filesystem, &stringF{RemountRO.String(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Procfs mount new procfs in sandbox
|
// Procfs mount new procfs in sandbox
|
||||||
// (--proc DEST)
|
// (--proc DEST)
|
||||||
func (c *Config) Procfs(dest string) *Config {
|
func (c *Config) Procfs(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{Procfs.Unwrap(), dest})
|
c.Filesystem = append(c.Filesystem, &stringF{Procfs.String(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevTmpfs mount new dev in sandbox
|
// DevTmpfs mount new dev in sandbox
|
||||||
// (--dev DEST)
|
// (--dev DEST)
|
||||||
func (c *Config) DevTmpfs(dest string) *Config {
|
func (c *Config) DevTmpfs(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{DevTmpfs.Unwrap(), dest})
|
c.Filesystem = append(c.Filesystem, &stringF{DevTmpfs.String(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mqueue mount new mqueue in sandbox
|
// Mqueue mount new mqueue in sandbox
|
||||||
// (--mqueue DEST)
|
// (--mqueue DEST)
|
||||||
func (c *Config) Mqueue(dest string) *Config {
|
func (c *Config) Mqueue(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{Mqueue.Unwrap(), dest})
|
c.Filesystem = append(c.Filesystem, &stringF{Mqueue.String(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,9 +71,6 @@ type Config struct {
|
|||||||
--ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST
|
--ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST
|
||||||
--exec-label LABEL Exec label for the sandbox
|
--exec-label LABEL Exec label for the sandbox
|
||||||
--file-label LABEL File label for temporary sandbox content
|
--file-label LABEL File label for temporary sandbox content
|
||||||
--file FD DEST Copy from FD to destination DEST
|
|
||||||
--bind-data FD DEST Copy from FD to file which is bind-mounted on DEST
|
|
||||||
--ro-bind-data FD DEST Copy from FD to file which is readonly bind-mounted on DEST
|
|
||||||
--add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)
|
--add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)
|
||||||
--block-fd FD Block on FD until some data to read is available
|
--block-fd FD Block on FD until some data to read is available
|
||||||
--userns-block-fd FD Block on FD until the user namespace is ready
|
--userns-block-fd FD Block on FD until the user namespace is ready
|
||||||
|
@ -6,24 +6,29 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_Args(t *testing.T) {
|
func TestConfig_Args(t *testing.T) {
|
||||||
|
seccomp.CPrintln = fmsg.Println
|
||||||
|
t.Cleanup(func() { seccomp.CPrintln = nil })
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
conf *bwrap.Config
|
conf *bwrap.Config
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "bind",
|
"bind", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
Bind("/etc", "/.fortify/etc").
|
Bind("/etc", "/.fortify/etc").
|
||||||
Bind("/etc", "/.fortify/etc", true).
|
Bind("/etc", "/.fortify/etc", true).
|
||||||
Bind("/run", "/.fortify/run", false, true).
|
Bind("/run", "/.fortify/run", false, true).
|
||||||
Bind("/sys/devices", "/.fortify/sys/devices", true, true).
|
Bind("/sys/devices", "/.fortify/sys/devices", true, true).
|
||||||
Bind("/dev/dri", "/.fortify/dev/dri", false, true, true).
|
Bind("/dev/dri", "/.fortify/dev/dri", false, true, true).
|
||||||
Bind("/dev/dri", "/.fortify/dev/dri", true, true, true),
|
Bind("/dev/dri", "/.fortify/dev/dri", true, true, true),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Bind("/etc", "/.fortify/etc")
|
// Bind("/etc", "/.fortify/etc")
|
||||||
@ -41,14 +46,13 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dir remount-ro proc dev mqueue",
|
"dir remount-ro proc dev mqueue", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
Dir("/.fortify").
|
Dir("/.fortify").
|
||||||
RemountRO("/home").
|
RemountRO("/home").
|
||||||
Procfs("/proc").
|
Procfs("/proc").
|
||||||
DevTmpfs("/dev").
|
DevTmpfs("/dev").
|
||||||
Mqueue("/dev/mqueue"),
|
Mqueue("/dev/mqueue"),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Dir("/.fortify")
|
// Dir("/.fortify")
|
||||||
@ -64,11 +68,10 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tmpfs",
|
"tmpfs", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
Tmpfs("/run/user", 8192).
|
Tmpfs("/run/user", 8192).
|
||||||
Tmpfs("/run/dbus", 8192, 0755),
|
Tmpfs("/run/dbus", 8192, 0755),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Tmpfs("/run/user", 8192)
|
// Tmpfs("/run/user", 8192)
|
||||||
@ -78,11 +81,10 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "symlink",
|
"symlink", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
Symlink("/.fortify/sbin/init", "/sbin/init").
|
Symlink("/.fortify/sbin/init", "/sbin/init").
|
||||||
Symlink("/.fortify/sbin/init", "/sbin/init", 0755),
|
Symlink("/.fortify/sbin/init", "/sbin/init", 0755),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Symlink("/.fortify/sbin/init", "/sbin/init")
|
// Symlink("/.fortify/sbin/init", "/sbin/init")
|
||||||
@ -92,12 +94,11 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "overlayfs",
|
"overlayfs", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
Overlay("/etc", "/etc").
|
Overlay("/etc", "/etc").
|
||||||
Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin").
|
Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin").
|
||||||
Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix"),
|
Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix"),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Overlay("/etc", "/etc")
|
// Overlay("/etc", "/etc")
|
||||||
@ -111,8 +112,23 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unshare",
|
"copy", (new(bwrap.Config)).
|
||||||
conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{
|
Write("/.fortify/version", make([]byte, 8)).
|
||||||
|
CopyBind("/etc/group", make([]byte, 8)).
|
||||||
|
CopyBind("/etc/passwd", make([]byte, 8), true),
|
||||||
|
[]string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Write("/.fortify/version", make([]byte, 8))
|
||||||
|
"--file", "3", "/.fortify/version",
|
||||||
|
// CopyBind("/etc/group", make([]byte, 8))
|
||||||
|
"--ro-bind-data", "4", "/etc/group",
|
||||||
|
// CopyBind("/etc/passwd", make([]byte, 8), true)
|
||||||
|
"--bind-data", "5", "/etc/passwd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unshare", &bwrap.Config{Unshare: &bwrap.UnshareConfig{
|
||||||
User: false,
|
User: false,
|
||||||
IPC: false,
|
IPC: false,
|
||||||
PID: false,
|
PID: false,
|
||||||
@ -120,14 +136,13 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
UTS: false,
|
UTS: false,
|
||||||
CGroup: false,
|
CGroup: false,
|
||||||
}},
|
}},
|
||||||
want: []string{"--disable-userns", "--assert-userns-disabled"},
|
[]string{"--disable-userns", "--assert-userns-disabled"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "uid gid sync",
|
"uid gid sync", (new(bwrap.Config)).
|
||||||
conf: (new(bwrap.Config)).
|
|
||||||
SetUID(1971).
|
SetUID(1971).
|
||||||
SetGID(100),
|
SetGID(100),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// SetUID(1971)
|
// SetUID(1971)
|
||||||
@ -137,16 +152,16 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "hostname chdir setenv unsetenv lockfile chmod",
|
"hostname chdir setenv unsetenv lockfile chmod syscall", &bwrap.Config{
|
||||||
conf: &bwrap.Config{
|
|
||||||
Hostname: "fortify",
|
Hostname: "fortify",
|
||||||
Chdir: "/.fortify",
|
Chdir: "/.fortify",
|
||||||
SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"},
|
SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"},
|
||||||
UnsetEnv: []string{"HOME", "HOST"},
|
UnsetEnv: []string{"HOME", "HOST"},
|
||||||
LockFile: []string{"/.fortify/lock"},
|
LockFile: []string{"/.fortify/lock"},
|
||||||
|
Syscall: new(bwrap.SyscallPolicy),
|
||||||
Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755},
|
Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755},
|
||||||
},
|
},
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
// Hostname: "fortify"
|
// Hostname: "fortify"
|
||||||
@ -160,19 +175,15 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
"--lock-file", "/.fortify/lock",
|
"--lock-file", "/.fortify/lock",
|
||||||
// SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}
|
// SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}
|
||||||
"--setenv", "FORTIFY_INIT", "/.fortify/sbin/init",
|
"--setenv", "FORTIFY_INIT", "/.fortify/sbin/init",
|
||||||
|
// Syscall: new(bwrap.SyscallPolicy),
|
||||||
|
"--seccomp", "3",
|
||||||
// Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}
|
// Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}
|
||||||
"--chmod", "755", "/.fortify/sbin/init",
|
"--chmod", "755", "/.fortify/sbin/init",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "xdg-dbus-proxy constraint sample",
|
"xdg-dbus-proxy constraint sample", (&bwrap.Config{Clearenv: true, DieWithParent: true}).
|
||||||
conf: (&bwrap.Config{
|
|
||||||
Unshare: nil,
|
|
||||||
UserNS: false,
|
|
||||||
Clearenv: true,
|
|
||||||
DieWithParent: true,
|
|
||||||
}).
|
|
||||||
Symlink("usr/bin", "/bin").
|
Symlink("usr/bin", "/bin").
|
||||||
Symlink("var/home", "/home").
|
Symlink("var/home", "/home").
|
||||||
Symlink("usr/lib", "/lib").
|
Symlink("usr/lib", "/lib").
|
||||||
@ -195,7 +206,7 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
Bind("/sysroot", "/sysroot").
|
Bind("/sysroot", "/sysroot").
|
||||||
Bind("/usr", "/usr").
|
Bind("/usr", "/usr").
|
||||||
Bind("/etc", "/etc"),
|
Bind("/etc", "/etc"),
|
||||||
want: []string{
|
[]string{
|
||||||
"--unshare-all", "--unshare-user",
|
"--unshare-all", "--unshare-user",
|
||||||
"--disable-userns", "--assert-userns-disabled",
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
"--clearenv", "--die-with-parent",
|
"--clearenv", "--die-with-parent",
|
||||||
@ -227,7 +238,7 @@ func TestConfig_Args(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) {
|
||||||
if got := tc.conf.Args(); !slices.Equal(got, tc.want) {
|
if got := tc.conf.Args(nil, new(proc.ExtraFilesPre), new([]proc.File)); !slices.Equal(got, tc.want) {
|
||||||
t.Errorf("Args() = %#v, want %#v", got, tc.want)
|
t.Errorf("Args() = %#v, want %#v", got, tc.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SyscallPolicy struct {
|
type SyscallPolicy struct {
|
||||||
@ -56,7 +55,7 @@ func (c *Config) seccompArgs() FDBuilder {
|
|||||||
for _, opt := range optCond {
|
for _, opt := range optCond {
|
||||||
if opt.v {
|
if opt.v {
|
||||||
opts |= opt.o
|
opts |= opt.o
|
||||||
if fmsg.Verbose() {
|
if seccomp.CPrintln != nil {
|
||||||
optd = append(optd, opt.d)
|
optd = append(optd, opt.d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,5 +81,5 @@ func (s *seccompBuilder) Append(args *[]string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
*args = append(*args, positionalArgs[Seccomp], strconv.Itoa(int(s.Fd())))
|
*args = append(*args, Seccomp.String(), strconv.Itoa(int(s.Fd())))
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,24 @@ package bwrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(new(PermConfig[SymlinkConfig]))
|
gob.Register(new(PermConfig[SymlinkConfig]))
|
||||||
gob.Register(new(PermConfig[*TmpfsConfig]))
|
gob.Register(new(PermConfig[*TmpfsConfig]))
|
||||||
gob.Register(new(OverlayConfig))
|
gob.Register(new(OverlayConfig))
|
||||||
|
gob.Register(new(DataConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
type PositionalArg int
|
type PositionalArg int
|
||||||
|
|
||||||
func (p PositionalArg) Unwrap() string {
|
func (p PositionalArg) String() string { return positionalArgs[p] }
|
||||||
return positionalArgs[p]
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Tmpfs PositionalArg = iota
|
Tmpfs PositionalArg = iota
|
||||||
@ -46,6 +49,10 @@ const (
|
|||||||
|
|
||||||
SyncFd
|
SyncFd
|
||||||
Seccomp
|
Seccomp
|
||||||
|
|
||||||
|
File
|
||||||
|
BindData
|
||||||
|
ROBindData
|
||||||
)
|
)
|
||||||
|
|
||||||
var positionalArgs = [...]string{
|
var positionalArgs = [...]string{
|
||||||
@ -76,6 +83,10 @@ var positionalArgs = [...]string{
|
|||||||
|
|
||||||
SyncFd: "--sync-fd",
|
SyncFd: "--sync-fd",
|
||||||
Seccomp: "--seccomp",
|
Seccomp: "--seccomp",
|
||||||
|
|
||||||
|
File: "--file",
|
||||||
|
BindData: "--bind-data",
|
||||||
|
ROBindData: "--ro-bind-data",
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermConfig[T FSBuilder] struct {
|
type PermConfig[T FSBuilder] struct {
|
||||||
@ -87,9 +98,7 @@ type PermConfig[T FSBuilder] struct {
|
|||||||
Inner T `json:"path"`
|
Inner T `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PermConfig[T]) Path() string {
|
func (p *PermConfig[T]) Path() string { return p.Inner.Path() }
|
||||||
return p.Inner.Path()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PermConfig[T]) Len() int {
|
func (p *PermConfig[T]) Len() int {
|
||||||
if p.Mode != nil {
|
if p.Mode != nil {
|
||||||
@ -101,7 +110,7 @@ func (p *PermConfig[T]) Len() int {
|
|||||||
|
|
||||||
func (p *PermConfig[T]) Append(args *[]string) {
|
func (p *PermConfig[T]) Append(args *[]string) {
|
||||||
if p.Mode != nil {
|
if p.Mode != nil {
|
||||||
*args = append(*args, Perms.Unwrap(), strconv.FormatInt(int64(*p.Mode), 8))
|
*args = append(*args, Perms.String(), strconv.FormatInt(int64(*p.Mode), 8))
|
||||||
}
|
}
|
||||||
p.Inner.Append(args)
|
p.Inner.Append(args)
|
||||||
}
|
}
|
||||||
@ -115,9 +124,7 @@ type TmpfsConfig struct {
|
|||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TmpfsConfig) Path() string {
|
func (t *TmpfsConfig) Path() string { return t.Dir }
|
||||||
return t.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TmpfsConfig) Len() int {
|
func (t *TmpfsConfig) Len() int {
|
||||||
if t.Size > 0 {
|
if t.Size > 0 {
|
||||||
@ -129,9 +136,9 @@ func (t *TmpfsConfig) Len() int {
|
|||||||
|
|
||||||
func (t *TmpfsConfig) Append(args *[]string) {
|
func (t *TmpfsConfig) Append(args *[]string) {
|
||||||
if t.Size > 0 {
|
if t.Size > 0 {
|
||||||
*args = append(*args, Size.Unwrap(), strconv.Itoa(t.Size))
|
*args = append(*args, Size.String(), strconv.Itoa(t.Size))
|
||||||
}
|
}
|
||||||
*args = append(*args, Tmpfs.Unwrap(), t.Dir)
|
*args = append(*args, Tmpfs.String(), t.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OverlayConfig struct {
|
type OverlayConfig struct {
|
||||||
@ -164,9 +171,7 @@ type OverlayConfig struct {
|
|||||||
Dest string `json:"dest"`
|
Dest string `json:"dest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OverlayConfig) Path() string {
|
func (o *OverlayConfig) Path() string { return o.Dest }
|
||||||
return o.Dest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OverlayConfig) Len() int {
|
func (o *OverlayConfig) Len() int {
|
||||||
// (--tmp-overlay DEST) or (--ro-overlay DEST)
|
// (--tmp-overlay DEST) or (--ro-overlay DEST)
|
||||||
@ -182,20 +187,20 @@ func (o *OverlayConfig) Len() int {
|
|||||||
func (o *OverlayConfig) Append(args *[]string) {
|
func (o *OverlayConfig) Append(args *[]string) {
|
||||||
// --overlay-src SRC
|
// --overlay-src SRC
|
||||||
for _, src := range o.Src {
|
for _, src := range o.Src {
|
||||||
*args = append(*args, OverlaySrc.Unwrap(), src)
|
*args = append(*args, OverlaySrc.String(), src)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Persist != nil {
|
if o.Persist != nil {
|
||||||
if o.Persist[0] != "" && o.Persist[1] != "" {
|
if o.Persist[0] != "" && o.Persist[1] != "" {
|
||||||
// --overlay RWSRC WORKDIR
|
// --overlay RWSRC WORKDIR
|
||||||
*args = append(*args, Overlay.Unwrap(), o.Persist[0], o.Persist[1])
|
*args = append(*args, Overlay.String(), o.Persist[0], o.Persist[1])
|
||||||
} else {
|
} else {
|
||||||
// --ro-overlay
|
// --ro-overlay
|
||||||
*args = append(*args, ROOverlay.Unwrap())
|
*args = append(*args, ROOverlay.String())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// --tmp-overlay
|
// --tmp-overlay
|
||||||
*args = append(*args, TmpOverlay.Unwrap())
|
*args = append(*args, TmpOverlay.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEST
|
// DEST
|
||||||
@ -204,26 +209,65 @@ func (o *OverlayConfig) Append(args *[]string) {
|
|||||||
|
|
||||||
type SymlinkConfig [2]string
|
type SymlinkConfig [2]string
|
||||||
|
|
||||||
func (s SymlinkConfig) Path() string {
|
func (s SymlinkConfig) Path() string { return s[1] }
|
||||||
return s[1]
|
func (s SymlinkConfig) Len() int { return 3 }
|
||||||
}
|
func (s SymlinkConfig) Append(args *[]string) { *args = append(*args, Symlink.String(), s[0], s[1]) }
|
||||||
|
|
||||||
func (s SymlinkConfig) Len() int {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SymlinkConfig) Append(args *[]string) {
|
|
||||||
*args = append(*args, Symlink.Unwrap(), s[0], s[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChmodConfig map[string]os.FileMode
|
type ChmodConfig map[string]os.FileMode
|
||||||
|
|
||||||
func (c ChmodConfig) Len() int {
|
func (c ChmodConfig) Len() int { return len(c) }
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ChmodConfig) Append(args *[]string) {
|
func (c ChmodConfig) Append(args *[]string) {
|
||||||
for path, mode := range c {
|
for path, mode := range c {
|
||||||
*args = append(*args, Chmod.Unwrap(), strconv.FormatInt(int64(mode), 8), path)
|
*args = append(*args, Chmod.String(), strconv.FormatInt(int64(mode), 8), path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DataWrite = iota
|
||||||
|
DataBind
|
||||||
|
DataROBind
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataConfig struct {
|
||||||
|
Dest string `json:"dest"`
|
||||||
|
Data []byte `json:"data,omitempty"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
proc.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DataConfig) Path() string { return d.Dest }
|
||||||
|
func (d *DataConfig) Len() int {
|
||||||
|
if d == nil || d.Data == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
func (d *DataConfig) Init(fd uintptr, v **os.File) uintptr {
|
||||||
|
if d.File != nil {
|
||||||
|
panic("file initialised twice")
|
||||||
|
}
|
||||||
|
d.File = proc.NewWriterTo(d)
|
||||||
|
return d.File.Init(fd, v)
|
||||||
|
}
|
||||||
|
func (d *DataConfig) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(d.Data)
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
func (d *DataConfig) Append(args *[]string) {
|
||||||
|
if d == nil || d.Data == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var a PositionalArg
|
||||||
|
switch d.Type {
|
||||||
|
case DataWrite:
|
||||||
|
a = File
|
||||||
|
case DataBind:
|
||||||
|
a = BindData
|
||||||
|
case DataROBind:
|
||||||
|
a = ROBindData
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid type %d", a))
|
||||||
|
}
|
||||||
|
|
||||||
|
*args = append(*args, a.String(), strconv.Itoa(int(d.Fd())), d.Dest)
|
||||||
|
}
|
||||||
|
@ -155,9 +155,8 @@ func bwrapStub() {
|
|||||||
DieWithParent: true,
|
DieWithParent: true,
|
||||||
AsInit: true,
|
AsInit: true,
|
||||||
}
|
}
|
||||||
args := sc.Args()
|
if _, err := MustNewCheckedArgs(sc.Args(nil, new(proc.ExtraFilesPre), new([]proc.File))).
|
||||||
sc.FDArgs(nil, &args, new(proc.ExtraFilesPre), new([]proc.File))
|
WriteTo(want); err != nil {
|
||||||
if _, err := MustNewCheckedArgs(args).WriteTo(want); err != nil {
|
|
||||||
panic("cannot read want: " + err.Error())
|
panic("cannot read want: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +62,6 @@ var testCasesNixos = []sealTestCase{
|
|||||||
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", 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
|
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/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/passwd", "u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n").
|
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/group", "fortify:x:1971:\n").
|
|
||||||
Link("/run/user/1971/wayland-0", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland").
|
|
||||||
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", 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").
|
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
|
||||||
CopyFile("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
|
CopyFile("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
|
||||||
@ -105,7 +102,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
"SHELL": "/run/current-system/sw/bin/zsh",
|
"SHELL": "/run/current-system/sw/bin/zsh",
|
||||||
"TERM": "xterm-256color",
|
"TERM": "xterm-256color",
|
||||||
"USER": "u0_a1",
|
"USER": "u0_a1",
|
||||||
"WAYLAND_DISPLAY": "/run/user/1971/wayland-0",
|
"WAYLAND_DISPLAY": "wayland-0",
|
||||||
"XDG_RUNTIME_DIR": "/run/user/1971",
|
"XDG_RUNTIME_DIR": "/run/user/1971",
|
||||||
"XDG_SESSION_CLASS": "user",
|
"XDG_SESSION_CLASS": "user",
|
||||||
"XDG_SESSION_TYPE": "tty",
|
"XDG_SESSION_TYPE": "tty",
|
||||||
@ -212,13 +209,15 @@ var testCasesNixos = []sealTestCase{
|
|||||||
Tmpfs("/run/user", 1048576).
|
Tmpfs("/run/user", 1048576).
|
||||||
Tmpfs("/run/user/1971", 8388608).
|
Tmpfs("/run/user/1971", 8388608).
|
||||||
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true).
|
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true).
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/passwd", "/etc/passwd").
|
CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/group", "/etc/group").
|
CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
|
||||||
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland", "/run/user/1971/wayland-0").
|
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0").
|
||||||
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native").
|
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native").
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus").
|
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus").
|
||||||
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
|
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", "/run/dbus/system_bus_socket").
|
||||||
Tmpfs("/var/run/nscd", 8192),
|
Tmpfs("/var/run/nscd", 8192).
|
||||||
|
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
|
||||||
|
Symlink("fortify", "/.fortify/sbin/init"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute).
|
Ensure("/tmp/fortify.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/fortify.1971/tmpdir/0", 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/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
|
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/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute).
|
Ephemeral(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/4a450b6596d7bc15bd01780eb9a607ac", acl.Execute),
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n").
|
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/group", "fortify:x:65534:\n"),
|
|
||||||
(&bwrap.Config{
|
(&bwrap.Config{
|
||||||
Net: true,
|
Net: true,
|
||||||
UserNS: true,
|
UserNS: true,
|
||||||
@ -154,9 +152,11 @@ var testCasesPd = []sealTestCase{
|
|||||||
Tmpfs("/run/user", 1048576).
|
Tmpfs("/run/user", 1048576).
|
||||||
Tmpfs("/run/user/65534", 8388608).
|
Tmpfs("/run/user/65534", 8388608).
|
||||||
Bind("/home/chronos", "/home/chronos", false, true).
|
Bind("/home/chronos", "/home/chronos", false, true).
|
||||||
Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/passwd", "/etc/passwd").
|
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
Bind("/tmp/fortify.1971/4a450b6596d7bc15bd01780eb9a607ac/group", "/etc/group").
|
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
|
||||||
Tmpfs("/var/run/nscd", 8192),
|
Tmpfs("/var/run/nscd", 8192).
|
||||||
|
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
|
||||||
|
Symlink("fortify", "/.fortify/sbin/init"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nixos permissive defaults chromium", new(stubNixOS),
|
"nixos permissive defaults chromium", new(stubNixOS),
|
||||||
@ -216,8 +216,6 @@ var testCasesPd = []sealTestCase{
|
|||||||
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", 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
|
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).
|
Ephemeral(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c", acl.Execute).
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n").
|
|
||||||
WriteType(system.Process, "/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "fortify:x:65534:\n").
|
|
||||||
Ensure("/tmp/fortify.1971/wayland", 0711).
|
Ensure("/tmp/fortify.1971/wayland", 0711).
|
||||||
Wayland("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
|
Wayland("/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").
|
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse").
|
||||||
@ -269,7 +267,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
"SHELL": "/run/current-system/sw/bin/zsh",
|
"SHELL": "/run/current-system/sw/bin/zsh",
|
||||||
"TERM": "xterm-256color",
|
"TERM": "xterm-256color",
|
||||||
"USER": "chronos",
|
"USER": "chronos",
|
||||||
"WAYLAND_DISPLAY": "/run/user/65534/wayland-0",
|
"WAYLAND_DISPLAY": "wayland-0",
|
||||||
"XDG_RUNTIME_DIR": "/run/user/65534",
|
"XDG_RUNTIME_DIR": "/run/user/65534",
|
||||||
"XDG_SESSION_CLASS": "user",
|
"XDG_SESSION_CLASS": "user",
|
||||||
"XDG_SESSION_TYPE": "tty",
|
"XDG_SESSION_TYPE": "tty",
|
||||||
@ -380,13 +378,15 @@ var testCasesPd = []sealTestCase{
|
|||||||
Tmpfs("/run/user", 1048576).
|
Tmpfs("/run/user", 1048576).
|
||||||
Tmpfs("/run/user/65534", 8388608).
|
Tmpfs("/run/user/65534", 8388608).
|
||||||
Bind("/home/chronos", "/home/chronos", false, true).
|
Bind("/home/chronos", "/home/chronos", false, true).
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/passwd", "/etc/passwd").
|
CopyBind("/etc/passwd", []byte("chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n")).
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/group", "/etc/group").
|
CopyBind("/etc/group", []byte("fortify:x:65534:\n")).
|
||||||
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0").
|
Bind("/tmp/fortify.1971/wayland/ebf083d1b175911782d413369b64ce7c", "/run/user/65534/wayland-0").
|
||||||
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native").
|
Bind("/run/user/1971/fortify/ebf083d1b175911782d413369b64ce7c/pulse", "/run/user/65534/pulse/native").
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/pulse-cookie", fst.Tmp+"/pulse-cookie").
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus").
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/bus", "/run/user/65534/bus").
|
||||||
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket").
|
Bind("/tmp/fortify.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", "/run/dbus/system_bus_socket").
|
||||||
Tmpfs("/var/run/nscd", 8192),
|
Tmpfs("/var/run/nscd", 8192).
|
||||||
|
Bind("/run/wrappers/bin/fortify", "/.fortify/sbin/fortify").
|
||||||
|
Symlink("fortify", "/.fortify/sbin/init"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,12 @@ type stubNixOS struct {
|
|||||||
usernameErr map[string]error
|
usernameErr map[string]error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Geteuid() int {
|
func (s *stubNixOS) Geteuid() int { return 1971 }
|
||||||
return 1971
|
func (s *stubNixOS) TempDir() string { return "/tmp" }
|
||||||
}
|
func (s *stubNixOS) MustExecutable() string { return "/run/wrappers/bin/fortify" }
|
||||||
|
func (s *stubNixOS) Exit(code int) { panic("called exit on stub with code " + strconv.Itoa(code)) }
|
||||||
|
func (s *stubNixOS) EvalSymlinks(path string) (string, error) { return path, nil }
|
||||||
|
func (s *stubNixOS) Uid(aid int) (int, error) { return 1000000 + 0*10000 + aid, nil }
|
||||||
|
|
||||||
func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
||||||
switch key {
|
switch key {
|
||||||
@ -39,10 +42,6 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) TempDir() string {
|
|
||||||
return "/tmp"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stubNixOS) LookPath(file string) (string, error) {
|
func (s *stubNixOS) LookPath(file string) (string, error) {
|
||||||
if s.lookPathErr != nil {
|
if s.lookPathErr != nil {
|
||||||
if err, ok := s.lookPathErr[file]; ok {
|
if err, ok := s.lookPathErr[file]; ok {
|
||||||
@ -60,10 +59,6 @@ func (s *stubNixOS) LookPath(file string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Executable() (string, error) {
|
|
||||||
return "/home/ophestra/.nix-profile/bin/fortify", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
|
func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "video":
|
case "video":
|
||||||
@ -127,14 +122,6 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) EvalSymlinks(path string) (string, error) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stubNixOS) Exit(code int) {
|
|
||||||
panic("called exit on stub with code " + strconv.Itoa(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stubNixOS) Paths() linux.Paths {
|
func (s *stubNixOS) Paths() linux.Paths {
|
||||||
return linux.Paths{
|
return linux.Paths{
|
||||||
SharePath: "/tmp/fortify.1971",
|
SharePath: "/tmp/fortify.1971",
|
||||||
@ -142,11 +129,3 @@ func (s *stubNixOS) Paths() linux.Paths {
|
|||||||
RunDirPath: "/run/user/1971/fortify",
|
RunDirPath: "/run/user/1971/fortify",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Uid(aid int) (int, error) {
|
|
||||||
return 1000000 + 0*10000 + aid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stubNixOS) SdBooted() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -15,6 +15,7 @@ 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/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
@ -133,7 +134,8 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
}
|
}
|
||||||
if seal.sys.user.username == "" {
|
if seal.sys.user.username == "" {
|
||||||
seal.sys.user.username = "chronos"
|
seal.sys.user.username = "chronos"
|
||||||
} else if !posixUsername.MatchString(seal.sys.user.username) {
|
} else if !posixUsername.MatchString(seal.sys.user.username) ||
|
||||||
|
len(seal.sys.user.username) >= internal.Sysconf_SC_LOGIN_NAME_MAX() {
|
||||||
return fmsg.WrapError(ErrName,
|
return fmsg.WrapError(ErrName,
|
||||||
fmt.Sprintf("invalid user name %q", seal.sys.user.username))
|
fmt.Sprintf("invalid user name %q", seal.sys.user.username))
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
|
"git.gensokyo.uk/security/fortify/wl"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,9 +28,6 @@ const (
|
|||||||
term = "TERM"
|
term = "TERM"
|
||||||
display = "DISPLAY"
|
display = "DISPLAY"
|
||||||
|
|
||||||
// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
|
|
||||||
waylandDisplay = "WAYLAND_DISPLAY"
|
|
||||||
|
|
||||||
pulseServer = "PULSE_SERVER"
|
pulseServer = "PULSE_SERVER"
|
||||||
pulseCookie = "PULSE_COOKIE"
|
pulseCookie = "PULSE_COOKIE"
|
||||||
|
|
||||||
@ -38,7 +36,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrWayland = errors.New(waylandDisplay + " unset")
|
|
||||||
ErrXDisplay = errors.New(display + " unset")
|
ErrXDisplay = errors.New(display + " unset")
|
||||||
|
|
||||||
ErrPulseCookie = errors.New("pulse cookie not present")
|
ErrPulseCookie = errors.New("pulse cookie not present")
|
||||||
@ -113,34 +110,25 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
sh = s
|
sh = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate /etc/passwd
|
// bind home directory
|
||||||
passwdPath := path.Join(seal.share, "passwd")
|
|
||||||
username := "chronos"
|
|
||||||
if seal.sys.user.username != "" {
|
|
||||||
username = seal.sys.user.username
|
|
||||||
}
|
|
||||||
homeDir := "/var/empty"
|
homeDir := "/var/empty"
|
||||||
if seal.sys.user.home != "" {
|
if seal.sys.user.home != "" {
|
||||||
homeDir = seal.sys.user.home
|
homeDir = seal.sys.user.home
|
||||||
}
|
}
|
||||||
|
username := "chronos"
|
||||||
// bind home directory
|
if seal.sys.user.username != "" {
|
||||||
|
username = seal.sys.user.username
|
||||||
|
}
|
||||||
seal.sys.bwrap.Bind(seal.sys.user.data, homeDir, false, true)
|
seal.sys.bwrap.Bind(seal.sys.user.data, homeDir, false, true)
|
||||||
seal.sys.bwrap.Chdir = homeDir
|
seal.sys.bwrap.Chdir = homeDir
|
||||||
|
|
||||||
seal.sys.bwrap.SetEnv["USER"] = username
|
|
||||||
seal.sys.bwrap.SetEnv["HOME"] = homeDir
|
seal.sys.bwrap.SetEnv["HOME"] = homeDir
|
||||||
|
seal.sys.bwrap.SetEnv["USER"] = username
|
||||||
|
|
||||||
passwd := username + ":x:" + seal.sys.mappedIDString + ":" + seal.sys.mappedIDString + ":Fortify:" + homeDir + ":" + sh + "\n"
|
// generate /etc/passwd and /etc/group
|
||||||
seal.sys.Write(passwdPath, passwd)
|
seal.sys.bwrap.CopyBind("/etc/passwd",
|
||||||
|
[]byte(username+":x:"+seal.sys.mappedIDString+":"+seal.sys.mappedIDString+":Fortify:"+homeDir+":"+sh+"\n"))
|
||||||
// write /etc/group
|
seal.sys.bwrap.CopyBind("/etc/group",
|
||||||
groupPath := path.Join(seal.share, "group")
|
[]byte("fortify:x:"+seal.sys.mappedIDString+":\n"))
|
||||||
seal.sys.Write(groupPath, "fortify:x:"+seal.sys.mappedIDString+":\n")
|
|
||||||
|
|
||||||
// bind /etc/passwd and /etc/group
|
|
||||||
seal.sys.bwrap.Bind(passwdPath, "/etc/passwd")
|
|
||||||
seal.sys.bwrap.Bind(groupPath, "/etc/group")
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Display servers
|
Display servers
|
||||||
@ -153,36 +141,36 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
|
|
||||||
// set up wayland
|
// set up wayland
|
||||||
if seal.et.Has(system.EWayland) {
|
if seal.et.Has(system.EWayland) {
|
||||||
var wp string
|
var socketPath string
|
||||||
if wd, ok := os.LookupEnv(waylandDisplay); !ok {
|
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok {
|
||||||
return fmsg.WrapError(ErrWayland,
|
fmsg.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName)
|
||||||
"WAYLAND_DISPLAY is not set")
|
socketPath = path.Join(seal.RuntimePath, wl.FallbackName)
|
||||||
|
} else if !path.IsAbs(name) {
|
||||||
|
socketPath = path.Join(seal.RuntimePath, name)
|
||||||
} else {
|
} else {
|
||||||
wp = path.Join(seal.RuntimePath, wd)
|
socketPath = name
|
||||||
}
|
}
|
||||||
|
|
||||||
w := path.Join(seal.sys.runtime, "wayland-0")
|
innerPath := path.Join(seal.sys.runtime, wl.FallbackName)
|
||||||
seal.sys.bwrap.SetEnv[waylandDisplay] = w
|
seal.sys.bwrap.SetEnv[wl.WaylandDisplay] = wl.FallbackName
|
||||||
|
|
||||||
if !seal.directWayland { // set up security-context-v1
|
if !seal.directWayland { // set up security-context-v1
|
||||||
wc := path.Join(seal.SharePath, "wayland")
|
socketDir := path.Join(seal.SharePath, "wayland")
|
||||||
wt := path.Join(wc, seal.id)
|
outerPath := path.Join(socketDir, seal.id)
|
||||||
seal.sys.Ensure(wc, 0711)
|
seal.sys.Ensure(socketDir, 0711)
|
||||||
appID := seal.fid
|
appID := seal.fid
|
||||||
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
|
appID = "uk.gensokyo.fortify." + seal.id
|
||||||
}
|
}
|
||||||
seal.sys.Wayland(wt, wp, appID, seal.id)
|
seal.sys.Wayland(outerPath, socketPath, appID, seal.id)
|
||||||
seal.sys.bwrap.Bind(wt, w)
|
seal.sys.bwrap.Bind(outerPath, innerPath)
|
||||||
} else { // bind mount wayland socket (insecure)
|
} else { // bind mount wayland socket (insecure)
|
||||||
// hardlink wayland socket
|
fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION")
|
||||||
wpi := path.Join(seal.shareLocal, "wayland")
|
seal.sys.bwrap.Bind(socketPath, innerPath)
|
||||||
seal.sys.Link(wp, wpi)
|
|
||||||
seal.sys.bwrap.Bind(wpi, w)
|
|
||||||
|
|
||||||
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
|
||||||
seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute)
|
seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +281,10 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
seal.sys.bwrap.Tmpfs(dest, 8*1024)
|
seal.sys.bwrap.Tmpfs(dest, 8*1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mount fortify in sandbox for init
|
||||||
|
seal.sys.bwrap.Bind(os.MustExecutable(), path.Join(fst.Tmp, "sbin/fortify"))
|
||||||
|
seal.sys.bwrap.Symlink("fortify", path.Join(fst.Tmp, "sbin/init"))
|
||||||
|
|
||||||
// append extra perms
|
// append extra perms
|
||||||
for _, p := range seal.extraPerms {
|
for _, p := range seal.extraPerms {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
@ -53,6 +53,7 @@ func queue(op dOp) {
|
|||||||
type dOp interface{ Do() }
|
type dOp interface{ Do() }
|
||||||
|
|
||||||
func Exit(code int) {
|
func Exit(code int) {
|
||||||
|
Resume() // resume here to avoid deadlock
|
||||||
queueSync.Wait()
|
queueSync.Wait()
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ type System interface {
|
|||||||
TempDir() string
|
TempDir() string
|
||||||
// LookPath provides [exec.LookPath].
|
// LookPath provides [exec.LookPath].
|
||||||
LookPath(file string) (string, error)
|
LookPath(file string) (string, error)
|
||||||
// Executable provides [os.Executable].
|
// MustExecutable provides [proc.MustExecutable].
|
||||||
Executable() (string, error)
|
MustExecutable() string
|
||||||
// LookupGroup provides [user.LookupGroup].
|
// LookupGroup provides [user.LookupGroup].
|
||||||
LookupGroup(name string) (*user.Group, error)
|
LookupGroup(name string) (*user.Group, error)
|
||||||
// ReadDir provides [os.ReadDir].
|
// ReadDir provides [os.ReadDir].
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
@ -32,7 +33,7 @@ func (s *Std) Geteuid() int { return os.Geteuid(
|
|||||||
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
|
||||||
func (s *Std) TempDir() string { return os.TempDir() }
|
func (s *Std) TempDir() string { return os.TempDir() }
|
||||||
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
|
||||||
func (s *Std) Executable() (string, error) { return os.Executable() }
|
func (s *Std) MustExecutable() string { return proc.MustExecutable() }
|
||||||
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
|
||||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
||||||
|
@ -121,21 +121,12 @@ func Main() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind fortify inside sandbox
|
|
||||||
var (
|
|
||||||
innerSbin = path.Join(fst.Tmp, "sbin")
|
|
||||||
innerFortify = path.Join(innerSbin, "fortify")
|
|
||||||
innerInit = path.Join(innerSbin, "init")
|
|
||||||
)
|
|
||||||
conf.Bind(proc.MustExecutable(), innerFortify)
|
|
||||||
conf.Symlink("fortify", innerInit)
|
|
||||||
|
|
||||||
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
|
||||||
if fmsg.Verbose() {
|
if fmsg.Verbose() {
|
||||||
seccomp.CPrintln = fmsg.Println
|
seccomp.CPrintln = fmsg.Println
|
||||||
}
|
}
|
||||||
if b, err := helper.NewBwrap(
|
if b, err := helper.NewBwrap(
|
||||||
conf, innerInit,
|
conf, path.Join(fst.Tmp, "sbin/init"),
|
||||||
nil, func(int, int) []string { return make([]string, 0) },
|
nil, func(int, int) []string { return make([]string, 0) },
|
||||||
extraFiles,
|
extraFiles,
|
||||||
syncFd,
|
syncFd,
|
||||||
|
@ -9,8 +9,19 @@ var (
|
|||||||
ErrDuplicate = errors.New("store contains duplicates")
|
ErrDuplicate = errors.New("store contains duplicates")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Joiner is the interface that wraps the Join method.
|
||||||
|
|
||||||
|
The Join function uses Joiner if available.
|
||||||
|
*/
|
||||||
|
type Joiner interface{ Join() (Entries, error) }
|
||||||
|
|
||||||
// Join returns joined state entries of all active aids.
|
// Join returns joined state entries of all active aids.
|
||||||
func Join(s Store) (Entries, error) {
|
func Join(s Store) (Entries, error) {
|
||||||
|
if j, ok := s.(Joiner); ok {
|
||||||
|
return j.Join()
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
aids []int
|
aids []int
|
||||||
entries = make(Entries)
|
entries = make(Entries)
|
6
internal/sysconf.go
Normal file
6
internal/sysconf.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
//#include <unistd.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func Sysconf_SC_LOGIN_NAME_MAX() int { return int(C.sysconf(C._SC_LOGIN_NAME_MAX)) }
|
@ -42,26 +42,9 @@ func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I {
|
|||||||
return sys
|
return sys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write registers an Op that writes dst with the contents of src.
|
|
||||||
func (sys *I) Write(dst, src string) *I {
|
|
||||||
return sys.WriteType(Process, dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteType registers a file writing Op labelled with type et.
|
|
||||||
func (sys *I) WriteType(et Enablement, dst, src string) *I {
|
|
||||||
sys.lock.Lock()
|
|
||||||
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileWrite, dst, src})
|
|
||||||
sys.lock.Unlock()
|
|
||||||
|
|
||||||
sys.UpdatePermType(et, dst, acl.Read)
|
|
||||||
|
|
||||||
return sys
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tmpfileCopy uint8 = iota
|
tmpfileCopy uint8 = iota
|
||||||
tmpfileLink
|
tmpfileLink
|
||||||
tmpfileWrite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tmpfile struct {
|
type Tmpfile struct {
|
||||||
@ -84,10 +67,6 @@ func (t *Tmpfile) apply(_ *I) error {
|
|||||||
fmsg.VPrintln("linking tmpfile", t)
|
fmsg.VPrintln("linking tmpfile", t)
|
||||||
return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
|
return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
|
||||||
fmt.Sprintf("cannot link tmpfile %q:", t.dst))
|
fmt.Sprintf("cannot link tmpfile %q:", t.dst))
|
||||||
case tmpfileWrite:
|
|
||||||
fmsg.VPrintln("writing", t)
|
|
||||||
return fmsg.WrapErrorSuffix(os.WriteFile(t.dst, []byte(t.src), 0600),
|
|
||||||
fmt.Sprintf("cannot write tmpfile %q:", t.dst))
|
|
||||||
default:
|
default:
|
||||||
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
||||||
}
|
}
|
||||||
@ -109,12 +88,7 @@ func (t *Tmpfile) Is(o Op) bool {
|
|||||||
return ok && t0 != nil && *t == *t0
|
return ok && t0 != nil && *t == *t0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tmpfile) Path() string {
|
func (t *Tmpfile) Path() string { return t.src }
|
||||||
if t.method == tmpfileWrite {
|
|
||||||
return fmt.Sprintf("(%d bytes of data)", len(t.src))
|
|
||||||
}
|
|
||||||
return t.src
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tmpfile) String() string {
|
func (t *Tmpfile) String() string {
|
||||||
switch t.method {
|
switch t.method {
|
||||||
@ -122,8 +96,6 @@ func (t *Tmpfile) String() string {
|
|||||||
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
||||||
case tmpfileLink:
|
case tmpfileLink:
|
||||||
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
||||||
case tmpfileWrite:
|
|
||||||
return fmt.Sprintf("%d bytes of data to %q", len(t.src), t.dst)
|
|
||||||
default:
|
default:
|
||||||
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
@ -83,47 +82,6 @@ func TestLinkFileType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
dst, src string
|
|
||||||
}{
|
|
||||||
{"/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
|
|
||||||
{"/etc/group", "fortify:x:65534:\n"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst, func(t *testing.T) {
|
|
||||||
sys := New(150)
|
|
||||||
sys.Write(tc.dst, tc.src)
|
|
||||||
(&tcOp{Process, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
|
|
||||||
&Tmpfile{Process, tmpfileWrite, tc.dst, tc.src},
|
|
||||||
&ACL{Process, tc.dst, []acl.Perm{acl.Read}},
|
|
||||||
}, "Write")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteType(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
et Enablement
|
|
||||||
dst, src string
|
|
||||||
}{
|
|
||||||
{Process, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
|
|
||||||
{Process, "/etc/group", "fortify:x:65534:\n"},
|
|
||||||
{User, "/etc/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n"},
|
|
||||||
{User, "/etc/group", "fortify:x:65534:\n"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run("write "+strconv.Itoa(len(tc.src))+" bytes to "+tc.dst+" with type "+TypeString(tc.et), func(t *testing.T) {
|
|
||||||
sys := New(150)
|
|
||||||
sys.WriteType(tc.et, tc.dst, tc.src)
|
|
||||||
(&tcOp{tc.et, "(" + strconv.Itoa(len(tc.src)) + " bytes of data)"}).test(t, sys.ops, []Op{
|
|
||||||
&Tmpfile{tc.et, tmpfileWrite, tc.dst, tc.src},
|
|
||||||
&ACL{tc.et, tc.dst, []acl.Perm{acl.Read}},
|
|
||||||
}, "WriteType")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTmpfile_String(t *testing.T) {
|
func TestTmpfile_String(t *testing.T) {
|
||||||
t.Run("invalid method panic", func(t *testing.T) {
|
t.Run("invalid method panic", func(t *testing.T) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -147,10 +105,6 @@ func TestTmpfile_String(t *testing.T) {
|
|||||||
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
|
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/wayland" from "/run/user/1971/wayland-0"`},
|
||||||
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
|
{tmpfileLink, "/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse", "/run/user/1971/pulse/native",
|
||||||
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
|
`"/run/user/1971/fortify/4b6bdc9182fb2f1d3a965c5fa8b9b66e/pulse" from "/run/user/1971/pulse/native"`},
|
||||||
{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd", "chronos:x:65534:65534:Fortify:/home/chronos:/run/current-system/sw/bin/zsh\n",
|
|
||||||
`75 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/passwd"`},
|
|
||||||
{tmpfileWrite, "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group", "fortify:x:65534:\n",
|
|
||||||
`17 bytes of data to "/tmp/fortify.1971/4b6bdc9182fb2f1d3a965c5fa8b9b66e/group"`},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -38,6 +38,10 @@ func (w Wayland) apply(sys *I) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := w.conn.Attach(w.pair[1]); err != nil {
|
if err := w.conn.Attach(w.pair[1]); err != nil {
|
||||||
|
// make console output less nasty
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
err = os.ErrNotExist
|
||||||
|
}
|
||||||
return fmsg.WrapErrorSuffix(err,
|
return fmsg.WrapErrorSuffix(err,
|
||||||
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
|
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
|
||||||
} else {
|
} else {
|
||||||
|
18
main.go
18
main.go
@ -13,6 +13,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
@ -23,6 +24,7 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
init0 "git.gensokyo.uk/security/fortify/internal/priv/init"
|
init0 "git.gensokyo.uk/security/fortify/internal/priv/init"
|
||||||
"git.gensokyo.uk/security/fortify/internal/priv/shim"
|
"git.gensokyo.uk/security/fortify/internal/priv/shim"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ func main() {
|
|||||||
fmt.Println(license)
|
fmt.Println(license)
|
||||||
fmsg.Exit(0)
|
fmsg.Exit(0)
|
||||||
case "template": // print full template configuration
|
case "template": // print full template configuration
|
||||||
printJSON(fst.Template())
|
printJSON(os.Stdout, false, fst.Template())
|
||||||
fmsg.Exit(0)
|
fmsg.Exit(0)
|
||||||
case "help": // print help message
|
case "help": // print help message
|
||||||
flag.CommandLine.Usage()
|
flag.CommandLine.Usage()
|
||||||
@ -127,7 +129,7 @@ func main() {
|
|||||||
// Ignore errors; set is set for ExitOnError.
|
// Ignore errors; set is set for ExitOnError.
|
||||||
_ = set.Parse(args[1:])
|
_ = set.Parse(args[1:])
|
||||||
|
|
||||||
printPs(short)
|
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sys.Paths().RunDirPath), short)
|
||||||
fmsg.Exit(0)
|
fmsg.Exit(0)
|
||||||
case "show": // pretty-print app info
|
case "show": // pretty-print app info
|
||||||
set := flag.NewFlagSet("show", flag.ExitOnError)
|
set := flag.NewFlagSet("show", flag.ExitOnError)
|
||||||
@ -139,14 +141,14 @@ func main() {
|
|||||||
|
|
||||||
switch len(set.Args()) {
|
switch len(set.Args()) {
|
||||||
case 0: // system
|
case 0: // system
|
||||||
printShowSystem(short)
|
printShowSystem(os.Stdout, short)
|
||||||
case 1: // instance
|
case 1: // instance
|
||||||
name := set.Args()[0]
|
name := set.Args()[0]
|
||||||
config, instance := tryShort(name)
|
config, instance := tryShort(name)
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = tryPath(name)
|
config = tryPath(name)
|
||||||
}
|
}
|
||||||
printShowInstance(instance, config, short)
|
printShowInstance(os.Stdout, time.Now().UTC(), instance, config, short)
|
||||||
default:
|
default:
|
||||||
fmsg.Fatal("show requires 1 argument")
|
fmsg.Fatal("show requires 1 argument")
|
||||||
}
|
}
|
||||||
@ -326,14 +328,14 @@ func runApp(config *fst.Config) {
|
|||||||
} else {
|
} else {
|
||||||
logWaitError(err)
|
logWaitError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rs.ExitCode == 0 {
|
||||||
|
rs.ExitCode = 126
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if rs.WaitErr != nil {
|
if rs.WaitErr != nil {
|
||||||
fmsg.Println("inner wait failed:", rs.WaitErr)
|
fmsg.Println("inner wait failed:", rs.WaitErr)
|
||||||
}
|
}
|
||||||
if rs.ExitCode < 0 {
|
|
||||||
fmsg.VPrintf("got negative exit %v", rs.ExitCode)
|
|
||||||
fmsg.Exit(1)
|
|
||||||
}
|
|
||||||
fmsg.Exit(rs.ExitCode)
|
fmsg.Exit(rs.ExitCode)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ let
|
|||||||
inherit (lib)
|
inherit (lib)
|
||||||
mkMerge
|
mkMerge
|
||||||
mkIf
|
mkIf
|
||||||
mkDefault
|
|
||||||
mapAttrs
|
mapAttrs
|
||||||
mergeAttrsList
|
mergeAttrsList
|
||||||
imap1
|
imap1
|
||||||
@ -46,10 +45,6 @@ in
|
|||||||
) "" cfg.users;
|
) "" cfg.users;
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.nix-daemon.unitConfig.RequiresMountsFor = [ "/etc/userdb" ];
|
|
||||||
|
|
||||||
services.userdbd.enable = mkDefault true;
|
|
||||||
|
|
||||||
home-manager =
|
home-manager =
|
||||||
let
|
let
|
||||||
privPackages = mapAttrs (username: fid: {
|
privPackages = mapAttrs (username: fid: {
|
||||||
@ -123,6 +118,7 @@ in
|
|||||||
};
|
};
|
||||||
map_real_uid = app.mapRealUid;
|
map_real_uid = app.mapRealUid;
|
||||||
no_new_session = app.tty;
|
no_new_session = app.tty;
|
||||||
|
direct_wayland = app.insecureWayland;
|
||||||
filesystem =
|
filesystem =
|
||||||
let
|
let
|
||||||
bind = src: { inherit src; };
|
bind = src: { inherit src; };
|
||||||
|
26
options.md
26
options.md
@ -36,7 +36,7 @@ package
|
|||||||
|
|
||||||
|
|
||||||
*Default:*
|
*Default:*
|
||||||
` <derivation fortify-0.2.13> `
|
` <derivation fortify-0.2.14> `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -425,6 +425,30 @@ null or string
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## environment\.fortify\.apps\.\*\.insecureWayland
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Whether to enable direct access to the Wayland socket\.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*Type:*
|
||||||
|
boolean
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*Default:*
|
||||||
|
` false `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*Example:*
|
||||||
|
` true `
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## environment\.fortify\.apps\.\*\.mapRealUid
|
## environment\.fortify\.apps\.\*\.mapRealUid
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ in
|
|||||||
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
mapRealUid = mkEnableOption "mapping to priv-user uid";
|
||||||
dev = mkEnableOption "access to all devices";
|
dev = mkEnableOption "access to all devices";
|
||||||
tty = mkEnableOption "access to the controlling terminal";
|
tty = mkEnableOption "access to the controlling terminal";
|
||||||
|
insecureWayland = mkEnableOption "direct access to the Wayland socket";
|
||||||
|
|
||||||
net = mkEnableOption "network access" // {
|
net = mkEnableOption "network access" // {
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "0.2.13";
|
version = "0.2.14";
|
||||||
|
|
||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "fortify-src";
|
name = "fortify-src";
|
||||||
|
195
print.go
195
print.go
@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
direct "os"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -16,7 +16,10 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printShowSystem(short bool) {
|
func printShowSystem(output io.Writer, short bool) {
|
||||||
|
t := newPrinter(output)
|
||||||
|
defer t.MustFlush()
|
||||||
|
|
||||||
info := new(fst.Info)
|
info := new(fst.Info)
|
||||||
|
|
||||||
// get fid by querying uid of aid 0
|
// get fid by querying uid of aid 0
|
||||||
@ -27,58 +30,55 @@ func printShowSystem(short bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
printJSON(info)
|
printJSON(output, short, info)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(direct.Stdout, 0, 1, 4, ' ', 0)
|
t.Printf("User:\t%d\n", info.User)
|
||||||
|
|
||||||
fmt.Fprintf(w, "User:\t%d\n", info.User)
|
|
||||||
|
|
||||||
if err := w.Flush(); err != nil {
|
|
||||||
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printShowInstance(instance *state.State, config *fst.Config, short bool) {
|
func printShowInstance(
|
||||||
|
output io.Writer, now time.Time,
|
||||||
|
instance *state.State, config *fst.Config,
|
||||||
|
short bool) {
|
||||||
if flagJSON {
|
if flagJSON {
|
||||||
if instance != nil {
|
if instance != nil {
|
||||||
printJSON(instance)
|
printJSON(output, short, instance)
|
||||||
} else {
|
} else {
|
||||||
printJSON(config)
|
printJSON(output, short, config)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
t := newPrinter(output)
|
||||||
w := tabwriter.NewWriter(direct.Stdout, 0, 1, 4, ' ', 0)
|
defer t.MustFlush()
|
||||||
|
|
||||||
if config.Confinement.Sandbox == nil {
|
if config.Confinement.Sandbox == nil {
|
||||||
fmt.Print("Warning: this configuration uses permissive defaults!\n\n")
|
mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance != nil {
|
if instance != nil {
|
||||||
fmt.Fprintf(w, "State\n")
|
t.Printf("State\n")
|
||||||
fmt.Fprintf(w, " Instance:\t%s (%d)\n", instance.ID.String(), instance.PID)
|
t.Printf(" Instance:\t%s (%d)\n", instance.ID.String(), instance.PID)
|
||||||
fmt.Fprintf(w, " Uptime:\t%s\n", now.Sub(instance.Time).Round(time.Second).String())
|
t.Printf(" Uptime:\t%s\n", now.Sub(instance.Time).Round(time.Second).String())
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w, "App\n")
|
t.Printf("App\n")
|
||||||
if config.ID != "" {
|
if config.ID != "" {
|
||||||
fmt.Fprintf(w, " ID:\t%d (%s)\n", config.Confinement.AppID, config.ID)
|
t.Printf(" ID:\t%d (%s)\n", config.Confinement.AppID, config.ID)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, " ID:\t%d\n", config.Confinement.AppID)
|
t.Printf(" ID:\t%d\n", config.Confinement.AppID)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " Enablements:\t%s\n", config.Confinement.Enablements.String())
|
t.Printf(" Enablements:\t%s\n", config.Confinement.Enablements.String())
|
||||||
if len(config.Confinement.Groups) > 0 {
|
if len(config.Confinement.Groups) > 0 {
|
||||||
fmt.Fprintf(w, " Groups:\t%q\n", config.Confinement.Groups)
|
t.Printf(" Groups:\t%q\n", config.Confinement.Groups)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " Directory:\t%s\n", config.Confinement.Outer)
|
t.Printf(" Directory:\t%s\n", config.Confinement.Outer)
|
||||||
if config.Confinement.Sandbox != nil {
|
if config.Confinement.Sandbox != nil {
|
||||||
sandbox := config.Confinement.Sandbox
|
sandbox := config.Confinement.Sandbox
|
||||||
if sandbox.Hostname != "" {
|
if sandbox.Hostname != "" {
|
||||||
fmt.Fprintf(w, " Hostname:\t%q\n", sandbox.Hostname)
|
t.Printf(" Hostname:\t%q\n", sandbox.Hostname)
|
||||||
}
|
}
|
||||||
flags := make([]string, 0, 7)
|
flags := make([]string, 0, 7)
|
||||||
writeFlag := func(name string, value bool) {
|
writeFlag := func(name string, value bool) {
|
||||||
@ -96,27 +96,27 @@ func printShowInstance(instance *state.State, config *fst.Config, short bool) {
|
|||||||
if len(flags) == 0 {
|
if len(flags) == 0 {
|
||||||
flags = append(flags, "none")
|
flags = append(flags, "none")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " Flags:\t%s\n", strings.Join(flags, " "))
|
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
|
||||||
|
|
||||||
etc := sandbox.Etc
|
etc := sandbox.Etc
|
||||||
if etc == "" {
|
if etc == "" {
|
||||||
etc = "/etc"
|
etc = "/etc"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " Etc:\t%s\n", etc)
|
t.Printf(" Etc:\t%s\n", etc)
|
||||||
|
|
||||||
if len(sandbox.Override) > 0 {
|
if len(sandbox.Override) > 0 {
|
||||||
fmt.Fprintf(w, " Overrides:\t%s\n", strings.Join(sandbox.Override, " "))
|
t.Printf(" Overrides:\t%s\n", strings.Join(sandbox.Override, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Env map[string]string `json:"env"`
|
// Env map[string]string `json:"env"`
|
||||||
// Link [][2]string `json:"symlink"`
|
// Link [][2]string `json:"symlink"`
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " Command:\t%s\n", strings.Join(config.Command, " "))
|
t.Printf(" Command:\t%s\n", strings.Join(config.Command, " "))
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
|
|
||||||
if !short {
|
if !short {
|
||||||
if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 {
|
if config.Confinement.Sandbox != nil && len(config.Confinement.Sandbox.Filesystem) > 0 {
|
||||||
fmt.Fprintf(w, "Filesystem\n")
|
t.Printf("Filesystem\n")
|
||||||
for _, f := range config.Confinement.Sandbox.Filesystem {
|
for _, f := range config.Confinement.Sandbox.Filesystem {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
continue
|
continue
|
||||||
@ -141,61 +141,54 @@ func printShowInstance(instance *state.State, config *fst.Config, short bool) {
|
|||||||
if f.Dst != "" {
|
if f.Dst != "" {
|
||||||
expr.WriteString(":" + f.Dst)
|
expr.WriteString(":" + f.Dst)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\n", expr.String())
|
t.Printf("%s\n", expr.String())
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
if len(config.Confinement.ExtraPerms) > 0 {
|
if len(config.Confinement.ExtraPerms) > 0 {
|
||||||
fmt.Fprintf(w, "Extra ACL\n")
|
t.Printf("Extra ACL\n")
|
||||||
for _, p := range config.Confinement.ExtraPerms {
|
for _, p := range config.Confinement.ExtraPerms {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, " %s\n", p.String())
|
t.Printf(" %s\n", p.String())
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printDBus := func(c *dbus.Config) {
|
printDBus := func(c *dbus.Config) {
|
||||||
fmt.Fprintf(w, " Filter:\t%v\n", c.Filter)
|
t.Printf(" Filter:\t%v\n", c.Filter)
|
||||||
if len(c.See) > 0 {
|
if len(c.See) > 0 {
|
||||||
fmt.Fprintf(w, " See:\t%q\n", c.See)
|
t.Printf(" See:\t%q\n", c.See)
|
||||||
}
|
}
|
||||||
if len(c.Talk) > 0 {
|
if len(c.Talk) > 0 {
|
||||||
fmt.Fprintf(w, " Talk:\t%q\n", c.Talk)
|
t.Printf(" Talk:\t%q\n", c.Talk)
|
||||||
}
|
}
|
||||||
if len(c.Own) > 0 {
|
if len(c.Own) > 0 {
|
||||||
fmt.Fprintf(w, " Own:\t%q\n", c.Own)
|
t.Printf(" Own:\t%q\n", c.Own)
|
||||||
}
|
}
|
||||||
if len(c.Call) > 0 {
|
if len(c.Call) > 0 {
|
||||||
fmt.Fprintf(w, " Call:\t%q\n", c.Call)
|
t.Printf(" Call:\t%q\n", c.Call)
|
||||||
}
|
}
|
||||||
if len(c.Broadcast) > 0 {
|
if len(c.Broadcast) > 0 {
|
||||||
fmt.Fprintf(w, " Broadcast:\t%q\n", c.Broadcast)
|
t.Printf(" Broadcast:\t%q\n", c.Broadcast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.Confinement.SessionBus != nil {
|
if config.Confinement.SessionBus != nil {
|
||||||
fmt.Fprintf(w, "Session bus\n")
|
t.Printf("Session bus\n")
|
||||||
printDBus(config.Confinement.SessionBus)
|
printDBus(config.Confinement.SessionBus)
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
}
|
}
|
||||||
if config.Confinement.SystemBus != nil {
|
if config.Confinement.SystemBus != nil {
|
||||||
fmt.Fprintf(w, "System bus\n")
|
t.Printf("System bus\n")
|
||||||
printDBus(config.Confinement.SystemBus)
|
printDBus(config.Confinement.SystemBus)
|
||||||
fmt.Fprintf(w, "\n")
|
t.Printf("\n")
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.Flush(); err != nil {
|
|
||||||
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printPs(short bool) {
|
func printPs(output io.Writer, now time.Time, s state.Store, short bool) {
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
var entries state.Entries
|
var entries state.Entries
|
||||||
s := state.NewMulti(sys.Paths().RunDirPath)
|
|
||||||
if e, err := state.Join(s); err != nil {
|
if e, err := state.Join(s); err != nil {
|
||||||
fmsg.Fatalf("cannot join store: %v", err)
|
fmsg.Fatalf("cannot join store: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -205,12 +198,12 @@ func printPs(short bool) {
|
|||||||
fmsg.Printf("cannot close store: %v", err)
|
fmsg.Printf("cannot close store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagJSON {
|
if !short && flagJSON {
|
||||||
es := make(map[string]*state.State, len(entries))
|
es := make(map[string]*state.State, len(entries))
|
||||||
for id, instance := range entries {
|
for id, instance := range entries {
|
||||||
es[id.String()] = instance
|
es[id.String()] = instance
|
||||||
}
|
}
|
||||||
printJSON(es)
|
printJSON(output, short, es)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +218,7 @@ func printPs(short bool) {
|
|||||||
|
|
||||||
// gracefully skip inconsistent states
|
// gracefully skip inconsistent states
|
||||||
if id != instance.ID {
|
if id != instance.ID {
|
||||||
fmt.Printf("possible store corruption: entry %s has id %s",
|
fmsg.Printf("possible store corruption: entry %s has id %s",
|
||||||
id.String(), instance.ID.String())
|
id.String(), instance.ID.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -239,24 +232,34 @@ func printPs(short bool) {
|
|||||||
for i, e := range exp {
|
for i, e := range exp {
|
||||||
v[i] = e.s
|
v[i] = e.s
|
||||||
}
|
}
|
||||||
printJSON(v)
|
printJSON(output, short, v)
|
||||||
} else {
|
} else {
|
||||||
for _, e := range exp {
|
for _, e := range exp {
|
||||||
fmt.Println(e.s[:8])
|
mustPrintln(output, e.s[:8])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer output to reduce terminal activity
|
t := newPrinter(output)
|
||||||
w := tabwriter.NewWriter(direct.Stdout, 0, 1, 4, ' ', 0)
|
defer t.MustFlush()
|
||||||
fmt.Fprintln(w, "\tInstance\tPID\tApp\tUptime\tEnablements\tCommand")
|
|
||||||
|
t.Println("\tInstance\tPID\tApp\tUptime\tEnablements\tCommand")
|
||||||
for _, e := range exp {
|
for _, e := range exp {
|
||||||
printInstance(w, e, now)
|
var (
|
||||||
}
|
es = "(No confinement information)"
|
||||||
if err := w.Flush(); err != nil {
|
cs = "(No command information)"
|
||||||
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
as = "(No configuration information)"
|
||||||
|
)
|
||||||
|
if e.Config != nil {
|
||||||
|
es = e.Config.Confinement.Enablements.String()
|
||||||
|
cs = fmt.Sprintf("%q", e.Config.Command)
|
||||||
|
as = strconv.Itoa(e.Config.Confinement.AppID)
|
||||||
|
}
|
||||||
|
t.Printf("\t%s\t%d\t%s\t%s\t%s\t%s\n",
|
||||||
|
e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String(), strings.TrimPrefix(es, ", "), cs)
|
||||||
}
|
}
|
||||||
|
t.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
type expandedStateEntry struct {
|
type expandedStateEntry struct {
|
||||||
@ -264,26 +267,48 @@ type expandedStateEntry struct {
|
|||||||
*state.State
|
*state.State
|
||||||
}
|
}
|
||||||
|
|
||||||
func printInstance(w *tabwriter.Writer, e *expandedStateEntry, now time.Time) {
|
func printJSON(output io.Writer, short bool, v any) {
|
||||||
var (
|
encoder := json.NewEncoder(output)
|
||||||
es = "(No confinement information)"
|
if !short {
|
||||||
cs = "(No command information)"
|
encoder.SetIndent("", " ")
|
||||||
as = "(No configuration information)"
|
|
||||||
)
|
|
||||||
if e.Config != nil {
|
|
||||||
es = e.Config.Confinement.Enablements.String()
|
|
||||||
cs = fmt.Sprintf("%q", e.Config.Command)
|
|
||||||
as = strconv.Itoa(e.Config.Confinement.AppID)
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "\t%s\t%d\t%s\t%s\t%s\t%s\n",
|
|
||||||
e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String(), strings.TrimPrefix(es, ", "), cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printJSON(v any) {
|
|
||||||
encoder := json.NewEncoder(direct.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
if err := encoder.Encode(v); err != nil {
|
if err := encoder.Encode(v); err != nil {
|
||||||
fmsg.Fatalf("cannot serialise: %v", err)
|
fmsg.Fatalf("cannot serialise: %v", err)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
|
||||||
|
|
||||||
|
type tp struct{ *tabwriter.Writer }
|
||||||
|
|
||||||
|
func (p *tp) Printf(format string, a ...any) {
|
||||||
|
if _, err := fmt.Fprintf(p, format, a...); err != nil {
|
||||||
|
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (p *tp) Println(a ...any) {
|
||||||
|
if _, err := fmt.Fprintln(p, a...); err != nil {
|
||||||
|
fmsg.Fatalf("cannot write to tabwriter: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (p *tp) MustFlush() {
|
||||||
|
if err := p.Writer.Flush(); err != nil {
|
||||||
|
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func mustPrint(output io.Writer, a ...any) {
|
||||||
|
if _, err := fmt.Fprint(output, a...); err != nil {
|
||||||
|
fmsg.Fatalf("cannot print: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func mustPrintln(output io.Writer, a ...any) {
|
||||||
|
if _, err := fmt.Fprintln(output, a...); err != nil {
|
||||||
|
fmsg.Fatalf("cannot print: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
671
print_test.go
Normal file
671
print_test.go
Normal file
@ -0,0 +1,671 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testID = fst.ID{
|
||||||
|
0x8e, 0x2c, 0x76, 0xb0,
|
||||||
|
0x66, 0xda, 0xbe, 0x57,
|
||||||
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
|
}
|
||||||
|
testState = &state.State{
|
||||||
|
ID: testID,
|
||||||
|
PID: 0xDEADBEEF,
|
||||||
|
Config: fst.Template(),
|
||||||
|
Time: testAppTime,
|
||||||
|
}
|
||||||
|
testTime = time.Unix(3752, 1).UTC()
|
||||||
|
testAppTime = time.Unix(0, 9).UTC()
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_printShowInstance(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
instance *state.State
|
||||||
|
config *fst.Config
|
||||||
|
short, json bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"config", nil, fst.Template(), false, false, `App
|
||||||
|
ID: 9 (org.chromium.Chromium)
|
||||||
|
Enablements: Wayland, D-Bus, PulseAudio
|
||||||
|
Groups: ["video"]
|
||||||
|
Directory: /var/lib/persist/home/org.chromium.Chromium
|
||||||
|
Hostname: "localhost"
|
||||||
|
Flags: userns net dev tty mapuid autoetc
|
||||||
|
Etc: /etc
|
||||||
|
Overrides: /var/run/nscd
|
||||||
|
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||||
|
|
||||||
|
Filesystem
|
||||||
|
+/nix/store
|
||||||
|
+/run/current-system
|
||||||
|
+/run/opengl-driver
|
||||||
|
+/var/db/nix-channels
|
||||||
|
w*/var/lib/fortify/u0/org.chromium.Chromium:/data/data/org.chromium.Chromium
|
||||||
|
d+/dev/dri
|
||||||
|
|
||||||
|
Extra ACL
|
||||||
|
--x+:/var/lib/fortify/u0
|
||||||
|
rwx:/var/lib/fortify/u0/org.chromium.Chromium
|
||||||
|
|
||||||
|
Session bus
|
||||||
|
Filter: true
|
||||||
|
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: map["org.freedesktop.portal.*":"*"]
|
||||||
|
Broadcast: map["org.freedesktop.portal.*":"@/org/freedesktop/portal/*"]
|
||||||
|
|
||||||
|
System bus
|
||||||
|
Filter: true
|
||||||
|
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"config pd", nil, new(fst.Config), false, false, `Warning: this configuration uses permissive defaults!
|
||||||
|
|
||||||
|
App
|
||||||
|
ID: 0
|
||||||
|
Enablements: (No enablements)
|
||||||
|
Directory:
|
||||||
|
Command:
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"config flag none", nil, &fst.Config{Confinement: fst.ConfinementConfig{Sandbox: new(fst.SandboxConfig)}}, false, false, `App
|
||||||
|
ID: 0
|
||||||
|
Enablements: (No enablements)
|
||||||
|
Directory:
|
||||||
|
Flags: none
|
||||||
|
Etc: /etc
|
||||||
|
Command:
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"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
|
||||||
|
ID: 0
|
||||||
|
Enablements: (No enablements)
|
||||||
|
Directory:
|
||||||
|
Flags: none
|
||||||
|
Etc: /etc
|
||||||
|
Command:
|
||||||
|
|
||||||
|
Filesystem
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
App
|
||||||
|
ID: 0
|
||||||
|
Enablements: (No enablements)
|
||||||
|
Directory:
|
||||||
|
Command:
|
||||||
|
|
||||||
|
Session bus
|
||||||
|
Filter: false
|
||||||
|
See: ["org.example.test"]
|
||||||
|
|
||||||
|
`},
|
||||||
|
|
||||||
|
{"instance", testState, fst.Template(), false, false, `State
|
||||||
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
||||||
|
Uptime: 1h2m32s
|
||||||
|
|
||||||
|
App
|
||||||
|
ID: 9 (org.chromium.Chromium)
|
||||||
|
Enablements: Wayland, D-Bus, PulseAudio
|
||||||
|
Groups: ["video"]
|
||||||
|
Directory: /var/lib/persist/home/org.chromium.Chromium
|
||||||
|
Hostname: "localhost"
|
||||||
|
Flags: userns net dev tty mapuid autoetc
|
||||||
|
Etc: /etc
|
||||||
|
Overrides: /var/run/nscd
|
||||||
|
Command: chromium --ignore-gpu-blocklist --disable-smooth-scrolling --enable-features=UseOzonePlatform --ozone-platform=wayland
|
||||||
|
|
||||||
|
Filesystem
|
||||||
|
+/nix/store
|
||||||
|
+/run/current-system
|
||||||
|
+/run/opengl-driver
|
||||||
|
+/var/db/nix-channels
|
||||||
|
w*/var/lib/fortify/u0/org.chromium.Chromium:/data/data/org.chromium.Chromium
|
||||||
|
d+/dev/dri
|
||||||
|
|
||||||
|
Extra ACL
|
||||||
|
--x+:/var/lib/fortify/u0
|
||||||
|
rwx:/var/lib/fortify/u0/org.chromium.Chromium
|
||||||
|
|
||||||
|
Session bus
|
||||||
|
Filter: true
|
||||||
|
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: map["org.freedesktop.portal.*":"*"]
|
||||||
|
Broadcast: map["org.freedesktop.portal.*":"@/org/freedesktop/portal/*"]
|
||||||
|
|
||||||
|
System bus
|
||||||
|
Filter: true
|
||||||
|
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"instance pd", testState, new(fst.Config), false, false, `Warning: this configuration uses permissive defaults!
|
||||||
|
|
||||||
|
State
|
||||||
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559)
|
||||||
|
Uptime: 1h2m32s
|
||||||
|
|
||||||
|
App
|
||||||
|
ID: 0
|
||||||
|
Enablements: (No enablements)
|
||||||
|
Directory:
|
||||||
|
Command:
|
||||||
|
|
||||||
|
`},
|
||||||
|
|
||||||
|
{"json nil", nil, nil, false, true, `null
|
||||||
|
`},
|
||||||
|
{"json instance", testState, nil, false, true, `{
|
||||||
|
"instance": [
|
||||||
|
142,
|
||||||
|
44,
|
||||||
|
118,
|
||||||
|
176,
|
||||||
|
102,
|
||||||
|
218,
|
||||||
|
190,
|
||||||
|
87,
|
||||||
|
76,
|
||||||
|
240,
|
||||||
|
115,
|
||||||
|
189,
|
||||||
|
180,
|
||||||
|
110,
|
||||||
|
181,
|
||||||
|
193
|
||||||
|
],
|
||||||
|
"pid": 3735928559,
|
||||||
|
"config": {
|
||||||
|
"id": "org.chromium.Chromium",
|
||||||
|
"command": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
|
],
|
||||||
|
"confinement": {
|
||||||
|
"app_id": 9,
|
||||||
|
"groups": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"home_inner": "/var/lib/fortify",
|
||||||
|
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
||||||
|
"sandbox": {
|
||||||
|
"hostname": "localhost",
|
||||||
|
"userns": true,
|
||||||
|
"net": true,
|
||||||
|
"dev": true,
|
||||||
|
"syscall": {
|
||||||
|
"compat": false,
|
||||||
|
"deny_devel": true,
|
||||||
|
"multiarch": true,
|
||||||
|
"linux32": false,
|
||||||
|
"can": false,
|
||||||
|
"bluetooth": false
|
||||||
|
},
|
||||||
|
"no_new_session": true,
|
||||||
|
"map_real_uid": true,
|
||||||
|
"env": {
|
||||||
|
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
"override": [
|
||||||
|
"/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"
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
{"json config", nil, fst.Template(), false, true, `{
|
||||||
|
"id": "org.chromium.Chromium",
|
||||||
|
"command": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
|
],
|
||||||
|
"confinement": {
|
||||||
|
"app_id": 9,
|
||||||
|
"groups": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"home_inner": "/var/lib/fortify",
|
||||||
|
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
||||||
|
"sandbox": {
|
||||||
|
"hostname": "localhost",
|
||||||
|
"userns": true,
|
||||||
|
"net": true,
|
||||||
|
"dev": true,
|
||||||
|
"syscall": {
|
||||||
|
"compat": false,
|
||||||
|
"deny_devel": true,
|
||||||
|
"multiarch": true,
|
||||||
|
"linux32": false,
|
||||||
|
"can": false,
|
||||||
|
"bluetooth": false
|
||||||
|
},
|
||||||
|
"no_new_session": true,
|
||||||
|
"map_real_uid": true,
|
||||||
|
"env": {
|
||||||
|
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
"override": [
|
||||||
|
"/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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
{
|
||||||
|
v := flagJSON
|
||||||
|
t.Cleanup(func() { flagJSON = v })
|
||||||
|
flagJSON = tc.json
|
||||||
|
}
|
||||||
|
|
||||||
|
output := new(strings.Builder)
|
||||||
|
printShowInstance(output, testTime, tc.instance, tc.config, tc.short)
|
||||||
|
if got := output.String(); got != tc.want {
|
||||||
|
t.Errorf("printShowInstance: got\n%s\nwant\n%s",
|
||||||
|
got, tc.want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_printPs(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
entries state.Entries
|
||||||
|
short, json bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"no entries", make(state.Entries), false, false, ` Instance PID App Uptime Enablements Command
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"no entries short", make(state.Entries), true, false, ``},
|
||||||
|
{"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", state.Entries{testID: testState}, false, false, ` Instance PID App Uptime Enablements Command
|
||||||
|
8e2c76b0 3735928559 9 1h2m32s Wayland, D-Bus, PulseAudio ["chromium" "--ignore-gpu-blocklist" "--disable-smooth-scrolling" "--enable-features=UseOzonePlatform" "--ozone-platform=wayland"]
|
||||||
|
|
||||||
|
`},
|
||||||
|
{"valid short", state.Entries{testID: testState}, true, false, `8e2c76b0
|
||||||
|
`},
|
||||||
|
{"valid json", state.Entries{testID: testState}, false, true, `{
|
||||||
|
"8e2c76b066dabe574cf073bdb46eb5c1": {
|
||||||
|
"instance": [
|
||||||
|
142,
|
||||||
|
44,
|
||||||
|
118,
|
||||||
|
176,
|
||||||
|
102,
|
||||||
|
218,
|
||||||
|
190,
|
||||||
|
87,
|
||||||
|
76,
|
||||||
|
240,
|
||||||
|
115,
|
||||||
|
189,
|
||||||
|
180,
|
||||||
|
110,
|
||||||
|
181,
|
||||||
|
193
|
||||||
|
],
|
||||||
|
"pid": 3735928559,
|
||||||
|
"config": {
|
||||||
|
"id": "org.chromium.Chromium",
|
||||||
|
"command": [
|
||||||
|
"chromium",
|
||||||
|
"--ignore-gpu-blocklist",
|
||||||
|
"--disable-smooth-scrolling",
|
||||||
|
"--enable-features=UseOzonePlatform",
|
||||||
|
"--ozone-platform=wayland"
|
||||||
|
],
|
||||||
|
"confinement": {
|
||||||
|
"app_id": 9,
|
||||||
|
"groups": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"username": "chronos",
|
||||||
|
"home_inner": "/var/lib/fortify",
|
||||||
|
"home": "/var/lib/persist/home/org.chromium.Chromium",
|
||||||
|
"sandbox": {
|
||||||
|
"hostname": "localhost",
|
||||||
|
"userns": true,
|
||||||
|
"net": true,
|
||||||
|
"dev": true,
|
||||||
|
"syscall": {
|
||||||
|
"compat": false,
|
||||||
|
"deny_devel": true,
|
||||||
|
"multiarch": true,
|
||||||
|
"linux32": false,
|
||||||
|
"can": false,
|
||||||
|
"bluetooth": false
|
||||||
|
},
|
||||||
|
"no_new_session": true,
|
||||||
|
"map_real_uid": true,
|
||||||
|
"env": {
|
||||||
|
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
||||||
|
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
"override": [
|
||||||
|
"/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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
{"valid short json", state.Entries{testID: testState}, true, true, `["8e2c76b066dabe574cf073bdb46eb5c1"]
|
||||||
|
`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
{
|
||||||
|
v := flagJSON
|
||||||
|
t.Cleanup(func() { flagJSON = v })
|
||||||
|
flagJSON = tc.json
|
||||||
|
}
|
||||||
|
|
||||||
|
output := new(strings.Builder)
|
||||||
|
printPs(output, testTime, stubStore(tc.entries), tc.short)
|
||||||
|
if got := output.String(); got != tc.want {
|
||||||
|
t.Errorf("printPs: got\n%s\nwant\n%s",
|
||||||
|
got, tc.want)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stubStore implements [state.Store] and returns test samples via [state.Joiner].
|
||||||
|
type stubStore state.Entries
|
||||||
|
|
||||||
|
func (s stubStore) Join() (state.Entries, error) { return state.Entries(s), nil }
|
||||||
|
func (s stubStore) Do(int, func(c state.Cursor)) (bool, error) { panic("unreachable") }
|
||||||
|
func (s stubStore) List() ([]int, error) { panic("unreachable") }
|
||||||
|
func (s stubStore) Close() error { return nil }
|
34
test.nix
34
test.nix
@ -79,8 +79,9 @@ nixosTest {
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
mkdir -p ~/.config/sway
|
mkdir -p ~/.config/sway
|
||||||
sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
|
(sed s/Mod4/Mod1/ /etc/sway/config &&
|
||||||
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' >> ~/.config/sway/config
|
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
|
||||||
|
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
|
||||||
|
|
||||||
sway --validate
|
sway --validate
|
||||||
systemd-cat --identifier=sway sway && touch /tmp/sway-exit-ok
|
systemd-cat --identifier=sway sway && touch /tmp/sway-exit-ok
|
||||||
@ -147,6 +148,18 @@ nixosTest {
|
|||||||
pulse = false;
|
pulse = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = "da-foot";
|
||||||
|
verbose = true;
|
||||||
|
insecureWayland = true;
|
||||||
|
share = pkgs.foot;
|
||||||
|
packages = [ pkgs.foot ];
|
||||||
|
command = "foot";
|
||||||
|
capability = {
|
||||||
|
dbus = false;
|
||||||
|
pulse = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name = "strace-failure";
|
name = "strace-failure";
|
||||||
verbose = true;
|
verbose = true;
|
||||||
@ -269,6 +282,9 @@ nixosTest {
|
|||||||
if output != "":
|
if output != "":
|
||||||
raise Exception(f"unexpected output\n{output}")
|
raise Exception(f"unexpected output\n{output}")
|
||||||
|
|
||||||
|
# Verify graceful failure on bad Wayland display name:
|
||||||
|
print(machine.fail("sudo -u alice -i fortify -v run --wayland true"))
|
||||||
|
|
||||||
# Start fortify permissive defaults within Wayland session:
|
# Start fortify permissive defaults within Wayland session:
|
||||||
fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-done')
|
fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-done')
|
||||||
machine.wait_for_file("/tmp/dbus-done")
|
machine.wait_for_file("/tmp/dbus-done")
|
||||||
@ -322,6 +338,20 @@ nixosTest {
|
|||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep alacritty")
|
machine.wait_until_fails("pgrep alacritty")
|
||||||
|
|
||||||
|
# Start app (foot) with direct Wayland access:
|
||||||
|
swaymsg("exec da-foot")
|
||||||
|
wait_for_window("u0_a4@machine")
|
||||||
|
machine.send_chars("clear; wayland-info && touch /tmp/success-direct\n")
|
||||||
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-direct")
|
||||||
|
collect_state_ui("foot_direct")
|
||||||
|
check_state("da-foot", 1)
|
||||||
|
# Verify acl on XDG_RUNTIME_DIR:
|
||||||
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004"))
|
||||||
|
machine.send_chars("exit\n")
|
||||||
|
machine.wait_until_fails("pgrep foot")
|
||||||
|
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
||||||
|
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004")
|
||||||
|
|
||||||
# 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"))
|
||||||
|
|
||||||
|
15
wl/consts.go
Normal file
15
wl/consts.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package wl
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WaylandDisplay contains the name of the server socket
|
||||||
|
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
|
||||||
|
// which is concatenated with XDG_RUNTIME_DIR
|
||||||
|
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171)
|
||||||
|
// or used as-is if absolute
|
||||||
|
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176).
|
||||||
|
WaylandDisplay = "WAYLAND_DISPLAY"
|
||||||
|
|
||||||
|
// FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset
|
||||||
|
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149).
|
||||||
|
FallbackName = "wayland-0"
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user