From f34dccb8f348894faa5f0ef9e4d61560012fcf41 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Aug 2021 23:29:29 +0200 Subject: [PATCH] Improve endpoint/rule lists and filtering of DNS requests --- firewall/master.go | 51 +++++++++++++-------------- profile/endpoints/endpoint-asn.go | 11 +++++- profile/endpoints/endpoint-country.go | 10 +++++- profile/endpoints/endpoint-ip.go | 2 +- profile/endpoints/endpoint-iprange.go | 3 +- profile/endpoints/endpoint-scopes.go | 2 +- profile/endpoints/endpoint.go | 10 +----- profile/endpoints/endpoints.go | 8 ++--- 8 files changed, 52 insertions(+), 45 deletions(-) diff --git a/firewall/master.go b/firewall/master.go index d2fe21bb..1687e187 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -54,13 +54,6 @@ var defaultDeciders = []deciderFn{ checkAutoPermitRelated, } -var dnsFromSystemResolverDeciders = []deciderFn{ - checkEndpointListsForSystemResolverDNSRequests, - checkConnectivityDomain, - checkBypassPrevention, - checkFilterLists, -} - // 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) { @@ -99,25 +92,18 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe conn.Entity.EnableCNAMECheck(ctx, layeredProfile.FilterCNAMEs()) conn.Entity.LoadLists(ctx) - // DNS request from the system resolver require a special decision process, - // because the original requesting process is not known. Here, we only check - // global-only and the most important per-app aspects. The resulting - // connection is then blocked when the original requesting process is known. - if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() { - // Run all deciders and return if they came to a conclusion. - done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, layeredProfile, pkt) - if !done { - conn.Accept("allowing system resolver dns request", noReasonOptionKey) - } - return - } - // Run all deciders and return if they came to a conclusion. done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt) if done { return } + // DNS Request are always default allowed, as the endpoint lists could not + // be checked fully. + if conn.Type == network.DNSRequest { + conn.Accept("allowing dns request", noReasonOptionKey) + } + // Deciders did not conclude, use default action. switch defaultAction { case profile.DefaultActionPermit: @@ -197,6 +183,14 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *pr } func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { + // DNS request from the system resolver require a special decision process, + // because the original requesting process is not known. Here, we only check + // global-only and the most important per-app aspects. The resulting + // connection is then blocked when the original requesting process is known. + if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() { + return checkEndpointListsForSystemResolverDNSRequests(ctx, conn, p) + } + var result endpoints.EPResult var reason endpoints.Reason @@ -210,7 +204,7 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil optionKey = profile.CfgOptionEndpointsKey } switch result { - case endpoints.Denied: + case endpoints.Denied, endpoints.MatchError: conn.DenyWithContext(reason.String(), optionKey, reason.Context()) return true case endpoints.Permitted: @@ -225,13 +219,13 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil // checkEndpointLists that is only meant for DNS queries by the system // resolver. It only checks the endpoint filter list of the local profile and // does not include the global profile. -func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { +func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile) bool { profileEndpoints := p.LocalProfile().GetEndpoints() if profileEndpoints.IsSet() { result, reason := profileEndpoints.Match(ctx, conn.Entity) if endpoints.IsDecision(result) { switch result { - case endpoints.Denied: + case endpoints.Denied, endpoints.MatchError: conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context()) return true case endpoints.Permitted: @@ -396,11 +390,13 @@ func checkResolverScope(_ context.Context, conn *network.Connection, p *profile. } func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { - if !p.DomainHeuristics() { + // Don't check if no domain is available. + if conn.Entity.Domain == "" { return false } - if conn.Entity.Domain == "" { + // Check if domain heuristics are enabled. + if !p.DomainHeuristics() { return false } @@ -485,10 +481,11 @@ func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *prof // 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 != "" { + // Don't check if no domain is available. + if conn.Entity.Domain == "" { return false, "" } - // don't check for unknown processes + // Don't check for unknown processes. if conn.Process().Pid < 0 { return false, "" } diff --git a/profile/endpoints/endpoint-asn.go b/profile/endpoints/endpoint-asn.go index 6c11f1a6..f5ababb5 100644 --- a/profile/endpoints/endpoint-asn.go +++ b/profile/endpoints/endpoint-asn.go @@ -22,9 +22,18 @@ type EndpointASN struct { // Matches checks whether the given entity matches this endpoint definition. func (ep *EndpointASN) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) { + if entity.IP == nil { + return NoMatch, nil + } + + if !entity.IPScope.IsGlobal() { + return NoMatch, nil + } + asn, ok := entity.GetASN(ctx) if !ok { - return Undeterminable, nil + asnStr := strconv.Itoa(int(ep.ASN)) + return MatchError, ep.makeReason(ep, asnStr, "ASN data not available to match") } if asn == ep.ASN { diff --git a/profile/endpoints/endpoint-country.go b/profile/endpoints/endpoint-country.go index d47d6e1f..b38fe42d 100644 --- a/profile/endpoints/endpoint-country.go +++ b/profile/endpoints/endpoint-country.go @@ -21,9 +21,17 @@ type EndpointCountry struct { // Matches checks whether the given entity matches this endpoint definition. func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) { + if entity.IP == nil { + return NoMatch, nil + } + + if !entity.IPScope.IsGlobal() { + return NoMatch, nil + } + country, ok := entity.GetCountry(ctx) if !ok { - return Undeterminable, nil + return MatchError, ep.makeReason(ep, country, "country data not available to match") } if country == ep.Country { diff --git a/profile/endpoints/endpoint-ip.go b/profile/endpoints/endpoint-ip.go index 08110247..9797eb8d 100644 --- a/profile/endpoints/endpoint-ip.go +++ b/profile/endpoints/endpoint-ip.go @@ -17,7 +17,7 @@ type EndpointIP struct { // Matches checks whether the given entity matches this endpoint definition. func (ep *EndpointIP) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { if entity.IP == nil { - return Undeterminable, nil + return NoMatch, nil } if ep.IP.Equal(entity.IP) { diff --git a/profile/endpoints/endpoint-iprange.go b/profile/endpoints/endpoint-iprange.go index d4be35db..6a0b713a 100644 --- a/profile/endpoints/endpoint-iprange.go +++ b/profile/endpoints/endpoint-iprange.go @@ -17,8 +17,9 @@ type EndpointIPRange struct { // Matches checks whether the given entity matches this endpoint definition. func (ep *EndpointIPRange) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { if entity.IP == nil { - return Undeterminable, nil + return NoMatch, nil } + if ep.Net.Contains(entity.IP) { return ep.match(ep, entity, ep.Net.String(), "IP is in") } diff --git a/profile/endpoints/endpoint-scopes.go b/profile/endpoints/endpoint-scopes.go index c6f05529..2753c115 100644 --- a/profile/endpoints/endpoint-scopes.go +++ b/profile/endpoints/endpoint-scopes.go @@ -33,7 +33,7 @@ type EndpointScope struct { // Matches checks whether the given entity matches this endpoint definition. func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { if entity.IP == nil { - return Undeterminable, nil + return NoMatch, nil } var scope uint8 diff --git a/profile/endpoints/endpoint.go b/profile/endpoints/endpoint.go index 16849f3c..013ec1d9 100644 --- a/profile/endpoints/endpoint.go +++ b/profile/endpoints/endpoint.go @@ -27,7 +27,7 @@ type EndpointBase struct { //nolint:maligned // TODO func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keyval ...interface{}) (EPResult, Reason) { result := ep.matchesPPP(entity) - if result == Undeterminable || result == NoMatch { + if result == NoMatch { return result, nil } @@ -57,10 +57,6 @@ func (ep *EndpointBase) makeReason(s fmt.Stringer, value, desc string, keyval .. func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) { // only check if protocol is defined if ep.Protocol > 0 { - // if protocol is unknown, return Undeterminable - if entity.Protocol == 0 { - return Undeterminable - } // if protocol does not match, return NoMatch if entity.Protocol != ep.Protocol { return NoMatch @@ -69,10 +65,6 @@ func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) { // only check if port is defined if ep.StartPort > 0 { - // if port is unknown, return Undeterminable - if entity.DstPort() == 0 { - return Undeterminable - } // if port does not match, return NoMatch if entity.DstPort() < ep.StartPort || entity.DstPort() > ep.EndPort { return NoMatch diff --git a/profile/endpoints/endpoints.go b/profile/endpoints/endpoints.go index 76c3d8ba..d4eada23 100644 --- a/profile/endpoints/endpoints.go +++ b/profile/endpoints/endpoints.go @@ -17,7 +17,7 @@ type EPResult uint8 // Endpoint matching return values const ( NoMatch EPResult = iota - Undeterminable + MatchError Denied Permitted ) @@ -25,7 +25,7 @@ const ( // IsDecision returns true if result represents a decision // and false if result is NoMatch or Undeterminable. func IsDecision(result EPResult) bool { - return result == Denied || result == Permitted || result == Undeterminable + return result == Denied || result == Permitted || result == MatchError } // ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching. @@ -88,8 +88,8 @@ func (epr EPResult) String() string { switch epr { case NoMatch: return "No Match" - case Undeterminable: - return "Undeterminable" + case MatchError: + return "Match Error" case Denied: return "Denied" case Permitted: