Improve decision tree, move resolver scope checking behind endpoint list rules

This commit is contained in:
Daniel 2021-04-16 21:36:16 +02:00
parent d626cea102
commit 6f9d17bba2

View file

@ -37,7 +37,7 @@ import (
const noReasonOptionKey = "" const noReasonOptionKey = ""
type deciderFn func(context.Context, *network.Connection, packet.Packet) bool type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfile, packet.Packet) bool
var defaultDeciders = []deciderFn{ var defaultDeciders = []deciderFn{
checkPortmasterConnection, checkPortmasterConnection,
@ -45,6 +45,7 @@ var defaultDeciders = []deciderFn{
checkConnectionType, checkConnectionType,
checkConnectionScope, checkConnectionScope,
checkEndpointLists, checkEndpointLists,
checkResolverScope,
checkConnectivityDomain, checkConnectivityDomain,
checkBypassPrevention, checkBypassPrevention,
checkFilterLists, checkFilterLists,
@ -97,7 +98,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
// connection is then blocked when the original requesting process is known. // connection is then blocked when the original requesting process is known.
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() { if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
// Run all deciders and return if they came to a conclusion. // Run all deciders and return if they came to a conclusion.
done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, pkt) done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, layeredProfile, pkt)
if !done { if !done {
conn.Accept("allowing system resolver dns request", noReasonOptionKey) conn.Accept("allowing system resolver dns request", noReasonOptionKey)
} }
@ -105,7 +106,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
} }
// Run all deciders and return if they came to a conclusion. // Run all deciders and return if they came to a conclusion.
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, pkt) done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt)
if done { if done {
return return
} }
@ -121,16 +122,14 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
} }
} }
func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) { func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, layeredProfile *profile.LayeredProfile, pkt packet.Packet) (done bool, defaultAction uint8) {
layeredProfile := conn.Process().Profile() // Read-lock all the profiles.
// Read-lock the all the profiles.
layeredProfile.LockForUsage() layeredProfile.LockForUsage()
defer layeredProfile.UnlockForUsage() defer layeredProfile.UnlockForUsage()
// Go though all deciders, return if one sets an action. // Go though all deciders, return if one sets an action.
for _, decider := range selectedDeciders { for _, decider := range selectedDeciders {
if decider(ctx, conn, pkt) { if decider(ctx, conn, layeredProfile, pkt) {
return true, profile.DefaultActionNotSet return true, profile.DefaultActionNotSet
} }
} }
@ -141,7 +140,7 @@ func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *networ
// checkPortmasterConnection allows all connection that originate from // checkPortmasterConnection allows all connection that originate from
// portmaster itself. // portmaster itself.
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool { func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, pkt packet.Packet) bool {
// Grant own outgoing connections. // Grant own outgoing connections.
if conn.Process().Pid == ownPID && if conn.Process().Pid == ownPID &&
(pkt == nil || pkt.IsOutbound()) { (pkt == nil || pkt.IsOutbound()) {
@ -155,7 +154,7 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pk
} }
// checkSelfCommunication checks if the process is communicating with itself. // checkSelfCommunication checks if the process is communicating with itself.
func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt packet.Packet) bool { func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, pkt packet.Packet) bool {
// check if process is communicating with itself // check if process is communicating with itself
if pkt != nil { if pkt != nil {
// TODO: evaluate the case where different IPs in the 127/8 net are used. // TODO: evaluate the case where different IPs in the 127/8 net are used.
@ -190,13 +189,10 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p
return false return false
} }
func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.Packet) bool { func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
var result endpoints.EPResult var result endpoints.EPResult
var reason endpoints.Reason var reason endpoints.Reason
// there must always be a profile.
p := conn.Process().Profile()
// check endpoints list // check endpoints list
var optionKey string var optionKey string
if conn.Inbound { if conn.Inbound {
@ -218,35 +214,41 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.
return false return false
} }
func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet.Packet) bool { func checkConnectionType(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile() switch {
case conn.Type != network.IPConnection:
// check conn type // Decider only applies to IP connections.
switch conn.Scope { return false
case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
if p.BlockInbound() { case conn.Inbound &&
if conn.Scope == network.IncomingHost { !conn.Entity.IPScope.IsLocalhost() &&
conn.Block("inbound connections blocked", profile.CfgOptionBlockInboundKey) p.BlockInbound():
} else {
// BlockInbound does not apply to the Localhost scope.
conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey) conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey)
}
return true return true
}
case network.PeerInternet: case conn.Entity.IPScope.IsGlobal() &&
// BlockP2P only applies to connections to the Internet conn.Entity.Domain == "" &&
if p.BlockP2P() { p.BlockP2P():
// BlockP2P only applies to the Global scope.
conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey) conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey)
return true return true
}
} default:
return false return false
}
} }
func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkConnectivityDomain(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
switch { switch {
case conn.Entity.Domain == "":
// Only applies if a domain is available.
return false
case netenv.GetOnlineStatus() > netenv.StatusPortal: case netenv.GetOnlineStatus() > netenv.StatusPortal:
// Special grant only applies if network status is Portal (or even more limited). // Special grant only applies if network status is Portal (or even more limited).
return false return false
@ -270,9 +272,7 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
} }
} }
func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkConnectionScope(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
// If we are handling a DNS request, check if we can immediately block it. // If we are handling a DNS request, check if we can immediately block it.
if conn.Type == network.DNSRequest { if conn.Type == network.DNSRequest {
// DNS is expected to resolve to LAN or Internet addresses. // DNS is expected to resolve to LAN or Internet addresses.
@ -307,8 +307,46 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
return true return true
} }
return false
}
func checkBypassPrevention(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
if p.PreventBypassing() {
// check for bypass protection
result, reason, reasonCtx := PreventBypassing(conn)
switch result {
case endpoints.Denied:
conn.BlockWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
return true
case endpoints.Permitted:
conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
return true
case endpoints.NoMatch:
}
}
return false
}
func checkFilterLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, pkt packet.Packet) bool {
// apply privacy filter lists
result, reason := p.MatchFilterLists(ctx, conn.Entity)
switch result {
case endpoints.Denied:
conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, 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 checkResolverScope(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
// If the IP address was resolved, check the scope of the resolver. // If the IP address was resolved, check the scope of the resolver.
switch { switch {
case conn.Type != network.IPConnection:
// Only applies to IP connections.
case !p.RemoveOutOfScopeDNS(): case !p.RemoveOutOfScopeDNS():
// Out of scope checking is not active. // Out of scope checking is not active.
case conn.Resolver == nil: case conn.Resolver == nil:
@ -328,43 +366,7 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
return false return false
} }
func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ 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, profile.CfgOptionPreventBypassingKey, reasonCtx)
return true
case endpoints.Permitted:
conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, 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(ctx, conn.Entity)
switch result {
case endpoints.Denied:
conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, 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 checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile()
if !p.DomainHeuristics() { if !p.DomainHeuristics() {
return false return false
} }
@ -422,7 +424,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack
return false return false
} }
func dropInbound(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func dropInbound(_ context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool {
// implicit default=block for inbound // implicit default=block for inbound
if conn.Inbound { if conn.Inbound {
conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey) conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey)
@ -431,9 +433,7 @@ func dropInbound(_ context.Context, conn *network.Connection, _ packet.Packet) b
return false return false
} }
func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool { func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
p := conn.Process().Profile()
// Auto permit is disabled for default action permit. // Auto permit is disabled for default action permit.
if p.DefaultAction() == profile.DefaultActionPermit { if p.DefaultAction() == profile.DefaultActionPermit {
return false return false