Compare commits

...

11 Commits

Author SHA1 Message Date
21735a8abe
release: 0.2.12
All checks were successful
Test / Create distribution (push) Successful in 2m25s
Release / Create release (push) Successful in 4m6s
Test / Run NixOS test (push) Successful in 4m49s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 13:40:48 +09:00
34272672b1
nix: verify silent output when not running with -v
All checks were successful
Test / Create distribution (push) Successful in 1m51s
Test / Run NixOS test (push) Successful in 4m40s
This checks behaviour of fmsg and seccomp.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 13:38:18 +09:00
7b96cd6ded
helper/seccomp: do not call F_println if not verbose
All checks were successful
Test / Create distribution (push) Successful in 1m42s
Test / Run NixOS test (push) Successful in 3m34s
This (slightly) improves performance.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 13:19:38 +09:00
163f15e93f
helper/seccomp: separate seccomp package
All checks were successful
Test / Create distribution (push) Successful in 1m39s
Test / Run NixOS test (push) Successful in 3m31s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 12:59:11 +09:00
016da20443
nix: expose compat flag in nixos module
All checks were successful
Test / Create distribution (push) Successful in 1m55s
Test / Run NixOS test (push) Successful in 4m6s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 12:42:48 +09:00
37780456a7
helper: block more unusual/privileged syscalls
All checks were successful
Test / Create distribution (push) Successful in 1m44s
Test / Run NixOS test (push) Successful in 3m35s
These are toggled by F_EXT and exposed as SyscallPolicy.Compat in the Go interface.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-25 12:35:47 +09:00
efacaa40fa
nix: set deny_devel correctly
All checks were successful
Test / Create distribution (push) Successful in 1m55s
Test / Run NixOS test (push) Successful in 3m51s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-24 00:50:35 +09:00
ad6d0ee55f
workflows: rename integration test artifact
All checks were successful
Test / Create distribution (push) Successful in 1m53s
Test / Run NixOS test (push) Successful in 3m45s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-24 00:30:39 +09:00
cf791469d8
workflows: gc store and purge old caches
All checks were successful
Test / Create distribution (push) Successful in 1m39s
Test / Run NixOS test (push) Successful in 3m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-24 00:25:57 +09:00
be14421775
workflows: merge test build job into test
All checks were successful
Test / Create distribution (push) Successful in 2m8s
Test / Run NixOS test (push) Successful in 3m57s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-24 00:22:44 +09:00
045983d7f4
wl: separate inline C
All checks were successful
Build / Create distribution (push) Successful in 1m41s
Test / Run NixOS test (push) Successful in 3m29s
Having a huge blurb of inline C hurts readability on web pages and some text editors.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-01-23 22:06:29 +09:00
18 changed files with 390 additions and 278 deletions

View File

@ -1,50 +0,0 @@
name: Build
on:
- push
- pull_request
jobs:
dist:
name: Create distribution
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # 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: nix-small-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: nix-small-${{ runner.os }}-
- name: Build for test
id: build-test
run: >-
export FORTIFY_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$FORTIFY_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$FORTIFY_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v3
with:
name: "fortify-${{ steps.build-test.outputs.rev }}"
path: result/*
retention-days: 1

View File

@ -5,7 +5,7 @@ on:
- pull_request - pull_request
jobs: jobs:
tests: test:
name: Run NixOS test name: Run NixOS test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -30,8 +30,13 @@ jobs:
- name: Restore Nix store - name: Restore Nix store
uses: nix-community/cache-nix-action@v5 uses: nix-community/cache-nix-action@v5
with: with:
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix') }} primary-key: flake-check-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: nix-${{ runner.os }}- 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: |
@ -41,6 +46,55 @@ jobs:
- name: Upload test output - name: Upload test output
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: "result" name: "nixos-vm-output"
path: result/*
retention-days: 1
dist:
name: Create distribution
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Nix
uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # 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
id: build-test
run: >-
export FORTIFY_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$FORTIFY_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$FORTIFY_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v3
with:
name: "fortify-${{ steps.build-test.outputs.rev }}"
path: result/* path: result/*
retention-days: 1 retention-days: 1

View File

@ -1,95 +0,0 @@
package bwrap
import (
"fmt"
"io"
"os"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
type SyscallPolicy struct {
DenyDevel bool `json:"deny_devel"`
Multiarch bool `json:"multiarch"`
Linux32 bool `json:"linux32"`
Can bool `json:"can"`
Bluetooth bool `json:"bluetooth"`
}
type seccompBuilder struct {
config *Config
}
func (s *seccompBuilder) Len() int {
if s == nil {
return 0
}
return 2
}
func (s *seccompBuilder) Append(args *[]string, extraFiles *[]*os.File) error {
if s == nil {
return nil
}
if f, err := s.config.resolveSeccomp(); err != nil {
return err
} else {
extraFile(args, extraFiles, positionalArgs[Seccomp], f)
return nil
}
}
func (c *Config) resolveSeccomp() (*os.File, error) {
if c.Syscall == nil {
return nil, nil
}
// resolve seccomp filter opts
var (
opts syscallOpts
optd []string
optCond = [...]struct {
v bool
o syscallOpts
d string
}{
{!c.UserNS, flagDenyNS, "denyns"},
{c.NewSession, flagDenyTTY, "denytty"},
{c.Syscall.DenyDevel, flagDenyDevel, "denydevel"},
{c.Syscall.Multiarch, flagMultiarch, "multiarch"},
{c.Syscall.Linux32, flagLinux32, "linux32"},
{c.Syscall.Can, flagCan, "can"},
{c.Syscall.Bluetooth, flagBluetooth, "bluetooth"},
}
)
if CPrintln != nil {
optd = make([]string, 1, len(optCond)+1)
optd[0] = "common"
}
for _, opt := range optCond {
if opt.v {
opts |= opt.o
if fmsg.Verbose() {
optd = append(optd, opt.d)
}
}
}
if CPrintln != nil {
CPrintln(fmt.Sprintf("seccomp flags: %s", optd))
}
// export seccomp filter to tmpfile
if f, err := tmpfile(); err != nil {
return nil, err
} else {
return f, exportAndSeek(f, opts)
}
}
func exportAndSeek(f *os.File, opts syscallOpts) error {
if err := exportFilter(f.Fd(), opts); err != nil {
return err
}
_, err := f.Seek(0, io.SeekStart)
return err
}

View File

@ -1,83 +1,90 @@
package bwrap package bwrap
/*
#cgo linux pkg-config: --static libseccomp
#include "seccomp-export.h"
*/
import "C"
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"runtime"
"git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal/fmsg"
) )
var CPrintln func(v ...any) type SyscallPolicy struct {
// disable fortify extensions
var resErr = [...]error{ Compat bool `json:"compat"`
0: nil, // deny development syscalls
1: errors.New("seccomp_init failed"), DenyDevel bool `json:"deny_devel"`
2: errors.New("seccomp_arch_add failed"), // deny multiarch/emulation syscalls
3: errors.New("seccomp_arch_add failed (multiarch)"), Multiarch bool `json:"multiarch"`
4: errors.New("internal libseccomp failure"), // allow PER_LINUX32
5: errors.New("seccomp_rule_add failed"), Linux32 bool `json:"linux32"`
6: errors.New("seccomp_export_bpf failed"), // allow AF_CAN
Can bool `json:"can"`
// allow AF_BLUETOOTH
Bluetooth bool `json:"bluetooth"`
} }
type ( type seccompBuilder struct {
syscallOpts = C.f_syscall_opts config *Config
)
const (
flagDenyNS syscallOpts = C.F_DENY_NS
flagDenyTTY syscallOpts = C.F_DENY_TTY
flagDenyDevel syscallOpts = C.F_DENY_DEVEL
flagMultiarch syscallOpts = C.F_MULTIARCH
flagLinux32 syscallOpts = C.F_LINUX32
flagCan syscallOpts = C.F_CAN
flagBluetooth syscallOpts = C.F_BLUETOOTH
)
func tmpfile() (*os.File, error) {
fd, err := C.f_tmpfile_fd()
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), "tmpfile"), err
} }
func exportFilter(fd uintptr, opts syscallOpts) error { func (s *seccompBuilder) Len() int {
var ( if s == nil {
arch C.uint32_t = 0 return 0
multiarch C.uint32_t = 0 }
) return 2
switch runtime.GOARCH {
case "386":
arch = C.SCMP_ARCH_X86
case "amd64":
arch = C.SCMP_ARCH_X86_64
multiarch = C.SCMP_ARCH_X86
case "arm":
arch = C.SCMP_ARCH_ARM
case "arm64":
arch = C.SCMP_ARCH_AARCH64
multiarch = C.SCMP_ARCH_ARM
} }
res, err := C.f_export_bpf(C.int(fd), arch, multiarch, opts) func (s *seccompBuilder) Append(args *[]string, extraFiles *[]*os.File) error {
if re := resErr[res]; re != nil { if s == nil {
if err == nil { return nil
return re
}
return fmt.Errorf("%s: %v", re.Error(), err)
} }
if f, err := s.config.resolveSeccomp(); err != nil {
return err return err
} else {
extraFile(args, extraFiles, positionalArgs[Seccomp], f)
return nil
}
} }
//export F_println func (c *Config) resolveSeccomp() (*os.File, error) {
func F_println(v *C.char) { if c.Syscall == nil {
if CPrintln != nil { return nil, nil
CPrintln(C.GoString(v)) }
// resolve seccomp filter opts
var (
opts seccomp.SyscallOpts
optd []string
optCond = [...]struct {
v bool
o seccomp.SyscallOpts
d string
}{
{!c.Syscall.Compat, seccomp.FlagExt, "fortify"},
{!c.UserNS, seccomp.FlagDenyNS, "denyns"},
{c.NewSession, seccomp.FlagDenyTTY, "denytty"},
{c.Syscall.DenyDevel, seccomp.FlagDenyDevel, "denydevel"},
{c.Syscall.Multiarch, seccomp.FlagMultiarch, "multiarch"},
{c.Syscall.Linux32, seccomp.FlagLinux32, "linux32"},
{c.Syscall.Can, seccomp.FlagCan, "can"},
{c.Syscall.Bluetooth, seccomp.FlagBluetooth, "bluetooth"},
}
)
if seccomp.CPrintln != nil {
optd = make([]string, 1, len(optCond)+1)
optd[0] = "common"
}
for _, opt := range optCond {
if opt.v {
opts |= opt.o
if fmsg.Verbose() {
optd = append(optd, opt.d)
} }
} }
}
if seccomp.CPrintln != nil {
seccomp.CPrintln(fmt.Sprintf("seccomp flags: %s", optd))
}
return seccomp.Export(opts)
}

17
helper/seccomp/export.go Normal file
View File

@ -0,0 +1,17 @@
package seccomp
import (
"io"
"os"
)
func Export(opts SyscallOpts) (f *os.File, err error) {
if f, err = tmpfile(); err != nil {
return
}
if err = exportFilter(f.Fd(), opts); err != nil {
return
}
_, err = f.Seek(0, io.SeekStart)
return
}

View File

@ -28,7 +28,7 @@ struct f_syscall_act {
#define LEN(arr) (sizeof(arr) / sizeof((arr)[0])) #define LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
#define SECCOMP_RULESET_ADD(ruleset) do { \ #define SECCOMP_RULESET_ADD(ruleset) do { \
F_println("adding seccomp ruleset \"" #ruleset "\""); \ if (opts & F_VERBOSE) F_println("adding seccomp ruleset \"" #ruleset "\""); \
for (int i = 0; i < LEN(ruleset); i++) { \ for (int i = 0; i < LEN(ruleset); i++) { \
assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \ assert(ruleset[i].m_errno == EPERM || ruleset[i].m_errno == ENOSYS); \
\ \
@ -89,6 +89,31 @@ int32_t f_export_bpf(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts o
{SCMP_SYS(migrate_pages), EPERM}, {SCMP_SYS(migrate_pages), EPERM},
}; };
// fortify: project-specific extensions
struct f_syscall_act deny_common_ext[] = {
// system calls for changing the system clock
{SCMP_SYS(adjtimex), EPERM},
{SCMP_SYS(clock_adjtime), EPERM},
{SCMP_SYS(clock_adjtime64), EPERM},
{SCMP_SYS(clock_settime), EPERM},
{SCMP_SYS(clock_settime64), EPERM},
{SCMP_SYS(settimeofday), EPERM},
// loading and unloading of kernel modules
{SCMP_SYS(delete_module), EPERM},
{SCMP_SYS(finit_module), EPERM},
{SCMP_SYS(init_module), EPERM},
// system calls for rebooting and reboot preparation
{SCMP_SYS(kexec_file_load), EPERM},
{SCMP_SYS(kexec_load), EPERM},
{SCMP_SYS(reboot), EPERM},
// system calls for enabling/disabling swap devices
{SCMP_SYS(swapoff), EPERM},
{SCMP_SYS(swapon), EPERM},
};
struct f_syscall_act deny_ns[] = { struct f_syscall_act deny_ns[] = {
// Don't allow subnamespace setups: // Don't allow subnamespace setups:
{SCMP_SYS(unshare), EPERM}, {SCMP_SYS(unshare), EPERM},
@ -126,6 +151,34 @@ int32_t f_export_bpf(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts o
{SCMP_SYS(mount_setattr), ENOSYS}, {SCMP_SYS(mount_setattr), ENOSYS},
}; };
// fortify: project-specific extensions
struct f_syscall_act deny_ns_ext[] = {
// changing file ownership
{SCMP_SYS(chown), EPERM},
{SCMP_SYS(chown32), EPERM},
{SCMP_SYS(fchown), EPERM},
{SCMP_SYS(fchown32), EPERM},
{SCMP_SYS(fchownat), EPERM},
{SCMP_SYS(lchown), EPERM},
{SCMP_SYS(lchown32), EPERM},
// system calls for changing user ID and group ID credentials
{SCMP_SYS(setgid), EPERM},
{SCMP_SYS(setgid32), EPERM},
{SCMP_SYS(setgroups), EPERM},
{SCMP_SYS(setgroups32), EPERM},
{SCMP_SYS(setregid), EPERM},
{SCMP_SYS(setregid32), EPERM},
{SCMP_SYS(setresgid), EPERM},
{SCMP_SYS(setresgid32), EPERM},
{SCMP_SYS(setresuid), EPERM},
{SCMP_SYS(setresuid32), EPERM},
{SCMP_SYS(setreuid), EPERM},
{SCMP_SYS(setreuid32), EPERM},
{SCMP_SYS(setuid), EPERM},
{SCMP_SYS(setuid32), EPERM},
};
struct f_syscall_act deny_tty[] = { struct f_syscall_act deny_tty[] = {
// Don't allow faking input to the controlling tty (CVE-2017-5226) // Don't allow faking input to the controlling tty (CVE-2017-5226)
{SCMP_SYS(ioctl), EPERM, &SCMP_A1(SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCSTI)}, {SCMP_SYS(ioctl), EPERM, &SCMP_A1(SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCSTI)},
@ -145,6 +198,22 @@ int32_t f_export_bpf(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts o
{SCMP_SYS(ptrace), EPERM} {SCMP_SYS(ptrace), EPERM}
}; };
struct f_syscall_act deny_emu[] = {
// modify_ldt is a historic source of interesting information leaks,
// so it's disabled as a hardening measure.
// However, it is required to run old 16-bit applications
// as well as some Wine patches, so it's allowed in multiarch.
{SCMP_SYS(modify_ldt), EPERM},
};
// fortify: project-specific extensions
struct f_syscall_act deny_emu_ext[] = {
{SCMP_SYS(subpage_prot), ENOSYS},
{SCMP_SYS(switch_endian), ENOSYS},
{SCMP_SYS(vm86), ENOSYS},
{SCMP_SYS(vm86old), ENOSYS},
};
// Blocklist all but unix, inet, inet6 and netlink // Blocklist all but unix, inet, inet6 and netlink
struct struct
{ {
@ -199,26 +268,11 @@ int32_t f_export_bpf(int fd, uint32_t arch, uint32_t multiarch, f_syscall_opts o
if (opts & F_DENY_NS) SECCOMP_RULESET_ADD(deny_ns); if (opts & F_DENY_NS) SECCOMP_RULESET_ADD(deny_ns);
if (opts & F_DENY_TTY) SECCOMP_RULESET_ADD(deny_tty); if (opts & F_DENY_TTY) SECCOMP_RULESET_ADD(deny_tty);
if (opts & F_DENY_DEVEL) SECCOMP_RULESET_ADD(deny_devel); if (opts & F_DENY_DEVEL) SECCOMP_RULESET_ADD(deny_devel);
if (!allow_multiarch) SECCOMP_RULESET_ADD(deny_emu);
if (!allow_multiarch) { if (opts & F_EXT) {
F_println("disabling modify_ldt"); SECCOMP_RULESET_ADD(deny_common_ext);
if (opts & F_DENY_NS) SECCOMP_RULESET_ADD(deny_ns_ext);
// modify_ldt is a historic source of interesting information leaks, if (!allow_multiarch) SECCOMP_RULESET_ADD(deny_emu_ext);
// so it's disabled as a hardening measure.
// However, it is required to run old 16-bit applications
// as well as some Wine patches, so it's allowed in multiarch.
ret = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(modify_ldt), 0);
// See above for the meaning of EFAULT.
if (ret == -EFAULT) {
// call fmsg here?
res = 4;
goto out;
} else if (ret < 0) {
res = 5;
errno = -ret;
goto out;
}
} }
// Socket filtering doesn't work on e.g. i386, so ignore failures here // Socket filtering doesn't work on e.g. i386, so ignore failures here

View File

@ -8,13 +8,15 @@
#endif #endif
typedef enum { typedef enum {
F_DENY_NS = 1 << 0, F_VERBOSE = 1 << 0,
F_DENY_TTY = 1 << 1, F_EXT = 1 << 1,
F_DENY_DEVEL = 1 << 2, F_DENY_NS = 1 << 2,
F_MULTIARCH = 1 << 3, F_DENY_TTY = 1 << 3,
F_LINUX32 = 1 << 4, F_DENY_DEVEL = 1 << 4,
F_CAN = 1 << 5, F_MULTIARCH = 1 << 5,
F_BLUETOOTH = 1 << 6, F_LINUX32 = 1 << 6,
F_CAN = 1 << 7,
F_BLUETOOTH = 1 << 8,
} f_syscall_opts; } f_syscall_opts;
extern void F_println(char *v); extern void F_println(char *v);

89
helper/seccomp/seccomp.go Normal file
View File

@ -0,0 +1,89 @@
package seccomp
/*
#cgo linux pkg-config: --static libseccomp
#include "seccomp-export.h"
*/
import "C"
import (
"errors"
"fmt"
"os"
"runtime"
)
var CPrintln func(v ...any)
var resErr = [...]error{
0: nil,
1: errors.New("seccomp_init failed"),
2: errors.New("seccomp_arch_add failed"),
3: errors.New("seccomp_arch_add failed (multiarch)"),
4: errors.New("internal libseccomp failure"),
5: errors.New("seccomp_rule_add failed"),
6: errors.New("seccomp_export_bpf failed"),
}
type SyscallOpts = C.f_syscall_opts
const (
flagVerbose SyscallOpts = C.F_VERBOSE
FlagExt SyscallOpts = C.F_EXT
FlagDenyNS SyscallOpts = C.F_DENY_NS
FlagDenyTTY SyscallOpts = C.F_DENY_TTY
FlagDenyDevel SyscallOpts = C.F_DENY_DEVEL
FlagMultiarch SyscallOpts = C.F_MULTIARCH
FlagLinux32 SyscallOpts = C.F_LINUX32
FlagCan SyscallOpts = C.F_CAN
FlagBluetooth SyscallOpts = C.F_BLUETOOTH
)
func tmpfile() (*os.File, error) {
fd, err := C.f_tmpfile_fd()
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), "tmpfile"), err
}
func exportFilter(fd uintptr, opts SyscallOpts) error {
var (
arch C.uint32_t = 0
multiarch C.uint32_t = 0
)
switch runtime.GOARCH {
case "386":
arch = C.SCMP_ARCH_X86
case "amd64":
arch = C.SCMP_ARCH_X86_64
multiarch = C.SCMP_ARCH_X86
case "arm":
arch = C.SCMP_ARCH_ARM
case "arm64":
arch = C.SCMP_ARCH_AARCH64
multiarch = C.SCMP_ARCH_ARM
}
// this removes repeated transitions between C and Go execution
// when producing log output via F_println and CPrintln is nil
if CPrintln != nil {
opts |= flagVerbose
}
res, err := C.f_export_bpf(C.int(fd), arch, multiarch, opts)
if re := resErr[res]; re != nil {
if err == nil {
return re
}
return fmt.Errorf("%s: %v", re.Error(), err)
}
return err
}
//export F_println
func F_println(v *C.char) {
if CPrintln != nil {
CPrintln(C.GoString(v))
}
}

View File

@ -8,7 +8,7 @@ import (
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/seccomp"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/proc" "git.gensokyo.uk/security/fortify/internal/proc"
@ -128,7 +128,7 @@ func Main() {
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() {
bwrap.CPrintln = fmsg.Println seccomp.CPrintln = fmsg.Println
} }
if b, err := helper.NewBwrap( if b, err := helper.NewBwrap(
conf, innerInit, conf, innerInit,

View File

@ -16,7 +16,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/seccomp"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/app" "git.gensokyo.uk/security/fortify/internal/app"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
@ -310,7 +310,7 @@ func runApp(config *fst.Config) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
if fmsg.Verbose() { if fmsg.Verbose() {
bwrap.CPrintln = fmsg.Println seccomp.CPrintln = fmsg.Println
} }
// handle signals for graceful shutdown // handle signals for graceful shutdown

View File

@ -118,7 +118,8 @@ in
env env
; ;
syscall = { syscall = {
inherit (app) devel multiarch bluetooth; inherit (app) compat multiarch bluetooth;
deny_devel = !app.devel;
}; };
map_real_uid = app.mapRealUid; map_real_uid = app.mapRealUid;
no_new_session = app.tty; no_new_session = app.tty;

View File

@ -36,7 +36,7 @@ package
*Default:* *Default:*
` <derivation fortify-0.2.11> ` ` <derivation fortify-0.2.12> `
@ -198,6 +198,30 @@ null or string
## environment\.fortify\.apps\.\*\.compat
Whether to enable disable syscall filter extensions\.
*Type:*
boolean
*Default:*
` false `
*Example:*
` true `
## environment\.fortify\.apps\.\*\.dbus\.session ## environment\.fortify\.apps\.\*\.dbus\.session

View File

@ -151,6 +151,7 @@ in
default = true; default = true;
}; };
compat = mkEnableOption "disable syscall filter extensions";
devel = mkEnableOption "development kernel APIs"; devel = mkEnableOption "development kernel APIs";
multiarch = mkEnableOption "multiarch kernel support"; multiarch = mkEnableOption "multiarch kernel support";
bluetooth = mkEnableOption "AF_BLUETOOTH socket operations"; bluetooth = mkEnableOption "AF_BLUETOOTH socket operations";

View File

@ -16,7 +16,7 @@
buildGoModule rec { buildGoModule rec {
pname = "fortify"; pname = "fortify";
version = "0.2.11"; version = "0.2.12";
src = builtins.path { src = builtins.path {
name = "fortify-src"; name = "fortify-src";
@ -63,7 +63,7 @@ buildGoModule rec {
makeBinaryWrapper makeBinaryWrapper
]; ];
preConfigure = '' preBuild = ''
HOME=$(mktemp -d) go generate ./... HOME=$(mktemp -d) go generate ./...
''; '';

View File

@ -237,8 +237,8 @@ nixosTest {
machine.succeed("rm -rf /tmp/src && cp -a '${self.packages.${system}.fortify.src}' /tmp/src") machine.succeed("rm -rf /tmp/src && cp -a '${self.packages.${system}.fortify.src}' /tmp/src")
machine.succeed("fortify-fhs -c '(cd /tmp/src && go generate ./... && go test ./... && touch /tmp/success-gotest)' &> /tmp/gotest &") machine.succeed("fortify-fhs -c '(cd /tmp/src && go generate ./... && go test ./... && touch /tmp/success-gotest)' &> /tmp/gotest &")
# To check sway's version: # To check fortify's version:
print(machine.succeed("sway --version")) print(machine.succeed("sudo -u alice -i fortify version"))
# Wait for Sway to complete startup: # Wait for Sway to complete startup:
machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/run/user/1000/wayland-1")
@ -254,6 +254,11 @@ nixosTest {
print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare")) print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare"))
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare") machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare")
# Verify silent output permissive defaults:
output = machine.succeed("sudo -u alice -i fortify run -a 0 true &>/dev/stdout")
if output != "":
raise Exception(f"unexpected output\n{output}")
# 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")

View File

@ -1,17 +1,7 @@
package wl #include "wayland-bind.h"
//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h"
//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c"
/*
#cgo linux pkg-config: --static wayland-client
#cgo freebsd openbsd LDFLAGS: -lwayland-client
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
@ -33,7 +23,7 @@ static const struct wl_registry_listener registry_listener = {
.global_remove = registry_handle_global_remove, .global_remove = registry_handle_global_remove,
}; };
static int32_t bind_wayland_fd(char *socket_path, int fd, const char *app_id, const char *instance_id, int sync_fd) { int32_t bind_wayland_fd(char *socket_path, int fd, const char *app_id, const char *instance_id, int sync_fd) {
int32_t res = 0; // refer to resErr for meaning int32_t res = 0; // refer to resErr for meaning
struct wl_display *display; struct wl_display *display;
@ -96,17 +86,3 @@ out:
free((void *)instance_id); free((void *)instance_id);
return res; return res;
} }
*/
import "C"
import "errors"
var resErr = [...]error{
0: nil,
1: errors.New("wl_display_connect_to_fd() failed"),
2: errors.New("wp_security_context_v1 not available"),
}
func bindWaylandFd(socketPath string, fd uintptr, appID, instanceID string, syncFD uintptr) error {
res := C.bind_wayland_fd(C.CString(socketPath), C.int(fd), C.CString(appID), C.CString(instanceID), C.int(syncFD))
return resErr[int32(res)]
}

3
wl/wayland-bind.h Normal file
View File

@ -0,0 +1,3 @@
#include <stdint.h>
int32_t bind_wayland_fd(char *socket_path, int fd, const char *app_id, const char *instance_id, int sync_fd);

24
wl/wl.go Normal file
View File

@ -0,0 +1,24 @@
package wl
//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h"
//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c"
/*
#cgo linux pkg-config: --static wayland-client
#cgo freebsd openbsd LDFLAGS: -lwayland-client
#include "wayland-bind.h"
*/
import "C"
import "errors"
var resErr = [...]error{
0: nil,
1: errors.New("wl_display_connect_to_fd() failed"),
2: errors.New("wp_security_context_v1 not available"),
}
func bindWaylandFd(socketPath string, fd uintptr, appID, instanceID string, syncFD uintptr) error {
res := C.bind_wayland_fd(C.CString(socketPath), C.int(fd), C.CString(appID), C.CString(instanceID), C.int(syncFD))
return resErr[int32(res)]
}