-
Notifications
You must be signed in to change notification settings - Fork 10
/
leakless.go
155 lines (128 loc) · 3.09 KB
/
leakless.go
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
152
153
154
155
//go:generate go run ./cmd/pack
package leakless
import (
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"github.com/ysmood/leakless/pkg/shared"
"github.com/ysmood/leakless/pkg/utils"
)
var leaklessBinaries = map[string]string{}
// Launcher struct
type Launcher struct {
// Lock for leakless.LockPort, default is 2978
Lock int
pid chan int
err string
}
// New leakless instance
func New() *Launcher {
return &Launcher{
Lock: 2978,
pid: make(chan int),
}
}
// Command will try to download the leakless bin and prefix the exec.Cmd with the leakless options.
func (l *Launcher) Command(name string, arg ...string) *exec.Cmd {
bin := ""
func() {
defer LockPort(l.Lock)()
bin = GetLeaklessBin()
}()
uid := fmt.Sprintf("%x", utils.RandBytes(16))
addr := l.serve(uid)
arg = append([]string{uid, addr, name}, arg...)
return exec.Command(bin, arg...)
}
// Pid signals the pid of the guarded sub-process. The channel may never receive the pid.
func (l *Launcher) Pid() chan int {
return l.pid
}
// Err message from the guard process
func (l *Launcher) Err() string {
return l.err
}
func (l *Launcher) serve(uid string) string {
srv, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic("[leakless] serve error: " + err.Error())
}
go func() {
defer func() { _ = srv.Close() }()
conn, err := srv.Accept()
if err != nil {
l.err = err.Error()
l.pid <- 0
return
}
enc := json.NewEncoder(conn)
err = enc.Encode(shared.Message{UID: uid})
if err != nil {
l.err = err.Error()
l.pid <- 0
return
}
dec := json.NewDecoder(conn)
var msg shared.Message
err = dec.Decode(&msg)
if err == nil {
l.err = msg.Error
l.pid <- msg.PID
}
_ = dec.Decode(&msg)
}()
return srv.Addr().String()
}
var leaklessDir = filepath.Join(os.TempDir(), fmt.Sprintf("leakless-%s-%s", runtime.GOARCH, shared.Version))
// GetLeaklessBin returns the executable path of the guard, if it doesn't exists create one.
func GetLeaklessBin() string {
bin := filepath.Join(leaklessDir, "leakless")
if runtime.GOOS == "windows" {
bin += ".exe"
}
if !utils.FileExists(bin) {
name := utils.GetTarget().BinName()
raw, err := base64.StdEncoding.DecodeString(leaklessBinaries[name])
utils.E(err)
gr, err := gzip.NewReader(bytes.NewBuffer(raw))
utils.E(err)
data, err := ioutil.ReadAll(gr)
utils.E(err)
utils.E(gr.Close())
err = utils.OutputFile(bin, data, nil)
utils.E(err)
utils.E(os.Chmod(bin, 0755))
}
return bin
}
// Support returns true if the OS is supported by leakless.
func Support() bool {
_, has := leaklessBinaries[utils.GetTarget().BinName()]
return has
}
// LockPort uses a tcp port to create a mutex lock for cross-process locking.
// It will poll the port to check if it's free.
func LockPort(port int) func() {
var l net.Listener
for {
var err error
l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err == nil {
break
}
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
}
return func() {
_ = l.Close()
}
}