-
-
Notifications
You must be signed in to change notification settings - Fork 274
/
master.go
369 lines (326 loc) Β· 10.3 KB
/
master.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package firewall
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/safing/portmaster/netenv"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/network/state"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/endpoints"
"github.com/agext/levenshtein"
)
// Call order:
//
// DNS Query:
// 1. DecideOnConnection
// is called when a DNS query is made, may set verdict to Undeterminable to permit a DNS reply.
// is called with a nil packet.
// 2. DecideOnResolvedDNS
// is called to (possibly) filter out A/AAAA records that the filter would deny later.
//
// Network Connection:
// 3. DecideOnConnection
// is called with the first packet of a network connection.
// DecideOnConnection makes a decision about a connection.
// When called, the connection and profile is already locked.
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
// update profiles and check if communication needs reevaluation
if conn.UpdateAndCheck() {
log.Tracer(ctx).Infof("filter: re-evaluating verdict on %s", conn)
conn.Verdict = network.VerdictUndecided
if conn.Entity != nil {
conn.Entity.ResetLists()
}
}
var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{
checkPortmasterConnection,
checkSelfCommunication,
checkProfileExists,
checkConnectionType,
checkConnectivityDomain,
checkConnectionScope,
checkEndpointLists,
checkBypassPrevention,
checkFilterLists,
checkInbound,
checkDefaultPermit,
checkAutoPermitRelated,
checkDefaultAction,
}
for _, decider := range deciders {
if decider(ctx, conn, pkt) {
return
}
}
// DefaultAction == DefaultActionBlock
conn.Deny("endpoint is not whitelisted (default=block)")
}
// checkPortmasterConnection allows all connection that originate from
// portmaster itself.
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
// grant self
if conn.Process().Pid == os.Getpid() {
log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
conn.Verdict = network.VerdictAccept
conn.Internal = true
return true
}
return false
}
// checkSelfCommunication checks if the process is communicating with itself.
func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
// check if process is communicating with itself
if pkt != nil {
// TODO: evaluate the case where different IPs in the 127/8 net are used.
pktInfo := pkt.Info()
if conn.Process().Pid >= 0 && pktInfo.Src.Equal(pktInfo.Dst) {
// get PID
otherPid, _, err := state.Lookup(&packet.Info{
Inbound: !pktInfo.Inbound, // we want to know the process on the other end
Version: pktInfo.Version,
Protocol: pktInfo.Protocol,
Src: pktInfo.Src,
SrcPort: pktInfo.SrcPort,
Dst: pktInfo.Dst,
DstPort: pktInfo.DstPort,
})
if err != nil {
log.Tracer(ctx).Warningf("filter: failed to find local peer process PID: %s", err)
} else {
// get primary process
otherProcess, err := process.GetOrFindPrimaryProcess(ctx, otherPid)
if err != nil {
log.Tracer(ctx).Warningf("filter: failed to find load local peer process with PID %d: %s", otherPid, err)
} else if otherProcess.Pid == conn.Process().Pid {
conn.Accept("connection to self")
conn.Internal = true
return true
}
}
}
}
return false
}
func checkProfileExists(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
if conn.Process().Profile() == nil {
conn.Block("unknown process or profile")
return true
}
return false
}
func checkEndpointLists(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
var result endpoints.EPResult
var reason endpoints.Reason
// there must always be a profile.
p := conn.Process().Profile()
// check endpoints list
if conn.Inbound {
result, reason = p.MatchServiceEndpoint(conn.Entity)
} else {
result, reason = p.MatchEndpoint(conn.Entity)
}
switch result {
case endpoints.Denied:
conn.DenyWithContext(reason.String(), reason.Context())
return true
case endpoints.Permitted:
conn.AcceptWithContext(reason.String(), reason.Context())
return true
}
return false
}
func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile()
// check conn type
switch conn.Scope {
case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
if p.BlockInbound() {
if conn.Scope == network.IncomingHost {
conn.Block("inbound connections blocked")
} else {
conn.Drop("inbound connections blocked")
}
return true
}
case network.PeerInternet:
// BlockP2P only applies to connections to the Internet
if p.BlockP2P() {
conn.Block("direct connections (P2P) blocked")
return true
}
}
return false
}
func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile()
switch {
case netenv.GetOnlineStatus() > netenv.StatusPortal:
// Special grant only applies if network status is Portal (or even more limited).
return false
case conn.Inbound:
// Special grant only applies to outgoing connections.
return false
case p.BlockScopeInternet():
// Special grant only applies if application is allowed to connect to the Internet.
return false
case netenv.IsConnectivityDomain(conn.Entity.Domain):
// Special grant!
conn.Accept("special grant for connectivity domain during network bootstrap")
return true
default:
// Not a special grant domain
return false
}
}
func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile()
// check scopes
if conn.Entity.IP != nil {
classification := netutils.ClassifyIP(conn.Entity.IP)
switch classification {
case netutils.Global, netutils.GlobalMulticast:
if p.BlockScopeInternet() {
conn.Deny("Internet access blocked") // Block Outbound / Drop Inbound
return true
}
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
if p.BlockScopeLAN() {
conn.Block("LAN access blocked") // Block Outbound / Drop Inbound
return true
}
case netutils.HostLocal:
if p.BlockScopeLocal() {
conn.Block("Localhost access blocked") // Block Outbound / Drop Inbound
return true
}
default: // netutils.Invalid
conn.Deny("invalid IP") // Block Outbound / Drop Inbound
return true
}
} else if conn.Entity.Domain != "" {
// DNS Query
// DNS is expected to resolve to LAN or Internet addresses
// TODO: handle domains mapped to localhost
if p.BlockScopeInternet() && p.BlockScopeLAN() {
conn.Block("Internet and LAN access blocked")
return true
}
}
return false
}
func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
if conn.Process().Profile().PreventBypassing() {
// check for bypass protection
result, reason, reasonCtx := PreventBypassing(conn)
switch result {
case endpoints.Denied:
conn.BlockWithContext("bypass prevention: "+reason, reasonCtx)
return true
case endpoints.Permitted:
conn.AcceptWithContext("bypass prevention: "+reason, reasonCtx)
return true
case endpoints.NoMatch:
}
}
return false
}
func checkFilterLists(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool {
// apply privacy filter lists
p := conn.Process().Profile()
result, reason := p.MatchFilterLists(conn.Entity)
switch result {
case endpoints.Denied:
conn.DenyWithContext(reason.String(), reason.Context())
return true
case endpoints.NoMatch:
// nothing to do
default:
log.Tracer(ctx).Debugf("filter: filter lists returned unsupported verdict: %s", result)
}
return false
}
func checkInbound(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
// implicit default=block for inbound
if conn.Inbound {
conn.Drop("endpoint is not whitelisted (incoming is always default=block)")
return true
}
return false
}
func checkDefaultPermit(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
// check default action
p := conn.Process().Profile()
if p.DefaultAction() == profile.DefaultActionPermit {
conn.Accept("endpoint is not blacklisted (default=permit)")
return true
}
return false
}
func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile()
if !p.DisableAutoPermit() {
related, reason := checkRelation(conn)
if related {
conn.Accept(reason)
return true
}
}
return false
}
func checkDefaultAction(_ context.Context, conn *network.Connection, pkt packet.Packet) bool {
p := conn.Process().Profile()
if p.DefaultAction() == profile.DefaultActionAsk {
prompt(conn, pkt)
return true
}
return false
}
// checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware.
func checkRelation(conn *network.Connection) (related bool, reason string) {
if conn.Entity.Domain != "" {
return false, ""
}
// don't check for unknown processes
if conn.Process().Pid < 0 {
return false, ""
}
pathElements := strings.Split(conn.Process().Path, string(filepath.Separator))
// only look at the last two path segments
if len(pathElements) > 2 {
pathElements = pathElements[len(pathElements)-2:]
}
domainElements := strings.Split(conn.Entity.Domain, ".")
var domainElement string
var processElement string
matchLoop:
for _, domainElement = range domainElements {
for _, pathElement := range pathElements {
if levenshtein.Match(domainElement, pathElement, nil) > 0.5 {
related = true
processElement = pathElement
break matchLoop
}
}
if levenshtein.Match(domainElement, conn.Process().Name, nil) > 0.5 {
related = true
processElement = conn.Process().Name
break matchLoop
}
if levenshtein.Match(domainElement, conn.Process().ExecName, nil) > 0.5 {
related = true
processElement = conn.Process().ExecName
break matchLoop
}
}
if related {
reason = fmt.Sprintf("domain is related to process: %s is related to %s", domainElement, processElement)
}
return related, reason
}