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>
90 lines
1.7 KiB
Go
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}
|
|
}
|