fortify/helper/seccomp/export.go
Ophestra c8f872cc9b
All checks were successful
Test / Create distribution (push) Successful in 1m10s
Test / Run NixOS test (push) Successful in 2m46s
helper/seccomp: implement reader interface via pipe
This also does not require the libc tmpfile call.

BPF programs emitted by libseccomp seems to be deterministic. The tests would catch regressions as it verifies the program against known good output backed by manual testing.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-02-03 18:10:29 +09:00

90 lines
1.7 KiB
Go

package seccomp
import (
"errors"
"io"
"io/fs"
"os"
"runtime"
"sync"
"sync/atomic"
"syscall"
)
// Export returns a temporary file containing a bpf program emitted by libseccomp.
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
}
/*
An Exporter writes a BPF program to an output stream.
Methods of Exporter are not safe for concurrent use.
An Exporter must not be copied after first use.
*/
type Exporter struct {
opts SyscallOpts
r, w *os.File
prepareOnce sync.Once
prepareErr error
closeErr atomic.Pointer[error]
exportErr <-chan error
}
func (e *Exporter) closeWrite() error {
if !e.closeErr.CompareAndSwap(nil, &fs.ErrInvalid) {
return *e.closeErr.Load()
}
err := e.w.Close()
e.closeErr.Store(&err)
return err
}
func (e *Exporter) prepare() error {
e.prepareOnce.Do(func() {
if r, w, err := os.Pipe(); err != nil {
e.prepareErr = err
return
} else {
e.r, e.w = r, w
}
ec := make(chan error, 1)
go func() { ec <- exportFilter(e.w.Fd(), e.opts); close(ec); _ = e.closeWrite() }()
e.exportErr = ec
runtime.SetFinalizer(e, (*Exporter).Close)
})
return e.prepareErr
}
func (e *Exporter) Read(p []byte) (n int, err error) {
if err = e.prepare(); err != nil {
return
}
return e.r.Read(p)
}
func (e *Exporter) Close() error {
if e.r == nil {
return syscall.EINVAL
}
runtime.SetFinalizer(e, nil)
// this hangs if the cgo thread fails to exit
return errors.Join(e.closeWrite(), <-e.exportErr)
}
func New(opts SyscallOpts) *Exporter {
return &Exporter{opts: opts}
}