aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper/bwrap.go
blob: 5b674b4931246871a7043f6a6006fbf4a6d81f8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package helper

import (
	"errors"
	"io"
	"os"
	"os/exec"
	"strconv"
	"sync"

	"git.ophivana.moe/security/fortify/helper/bwrap"
	"git.ophivana.moe/security/fortify/internal/proc"
)

// BubblewrapName is the file name or path to bubblewrap.
var BubblewrapName = "bwrap"

type bubblewrap struct {
	// bwrap child file name
	name string

	// bwrap pipes
	p *pipes
	// sync pipe
	sync *os.File
	// returns an array of arguments passed directly
	// to the child process spawned by bwrap
	argF func(argsFD, statFD int) []string

	// pipes received by the child
	// nil if no pipes are required
	cp *pipes

	lock sync.RWMutex
	*exec.Cmd
}

func (b *bubblewrap) StartNotify(ready chan error) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if ready != nil && b.cp == nil {
		panic("attempted to start with status monitoring on a bwrap child initialised without pipes")
	}

	// Check for doubled Start calls before we defer failure cleanup. If the prior
	// call to Start succeeded, we don't want to spuriously close its pipes.
	if b.Cmd.Process != nil {
		return errors.New("exec: already started")
	}

	// prepare bwrap pipe and args
	if argsFD, _, err := b.p.prepareCmd(b.Cmd); err != nil {
		return err
	} else {
		b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(argsFD), "--", b.name)
	}

	// prepare child args and pipes if enabled
	if b.cp != nil {
		b.cp.ready = ready
		if argsFD, statFD, err := b.cp.prepareCmd(b.Cmd); err != nil {
			return err
		} else {
			b.Cmd.Args = append(b.Cmd.Args, b.argF(argsFD, statFD)...)
		}
	} else {
		b.Cmd.Args = append(b.Cmd.Args, b.argF(-1, -1)...)
	}

	if ready != nil {
		b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=1")
	} else if b.cp != nil {
		b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=0")
	} else {
		b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=-1")
	}

	if b.sync != nil {
		b.Cmd.Args = append(b.Cmd.Args, "--sync-fd", strconv.Itoa(int(proc.ExtraFile(b.Cmd, b.sync))))
	}

	if err := b.Cmd.Start(); err != nil {
		return err
	}

	// write bwrap args first
	if err := b.p.readyWriteArgs(); err != nil {
		return err
	}

	// write child args if enabled
	if b.cp != nil {
		if err := b.cp.readyWriteArgs(); err != nil {
			return err
		}
	}

	return nil
}

func (b *bubblewrap) Close() error {
	if b.cp == nil {
		panic("attempted to close bwrap child initialised without pipes")
	}

	return b.cp.closeStatus()
}

func (b *bubblewrap) Start() error {
	return b.StartNotify(nil)
}

func (b *bubblewrap) Unwrap() *exec.Cmd {
	return b.Cmd
}

// MustNewBwrap initialises a new Bwrap instance with wt as the null-terminated argument writer.
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
// Function argF returns an array of arguments passed directly to the child process.
func MustNewBwrap(conf *bwrap.Config, wt io.WriterTo, name string, argF func(argsFD, statFD int) []string) Helper {
	b, err := NewBwrap(conf, wt, name, argF)
	if err != nil {
		panic(err.Error())
	} else {
		return b
	}
}

// NewBwrap initialises a new Bwrap instance with wt as the null-terminated argument writer.
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
// Function argF returns an array of arguments passed directly to the child process.
func NewBwrap(conf *bwrap.Config, wt io.WriterTo, name string, argF func(argsFD, statFD int) []string) (Helper, error) {
	b := new(bubblewrap)

	if args, err := NewCheckedArgs(conf.Args()); err != nil {
		return nil, err
	} else {
		b.p = &pipes{args: args}
	}

	b.sync = conf.Sync()
	b.argF = argF
	b.name = name
	if wt != nil {
		b.cp = &pipes{args: wt}
	}
	b.Cmd = execCommand(BubblewrapName)

	return b, nil
}