From 943246c74753ce00c02249f2abfa760145236c03 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Apr 2020 11:01:26 +0200 Subject: [PATCH 1/3] Add support for firefox canary domain for disabling DoH --- firewall/master.go | 14 ++++++++++++-- intel/entity.go | 1 - profile/config.go | 20 ++++++++++++++++++++ profile/profile-layered.go | 24 +++++++++++++++++++++++- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/firewall/master.go b/firewall/master.go index 9650b625..3dd9f8e8 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -141,9 +141,19 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint: } } + // check for bypass protection + result, reason := p.MatchBypassProtection(conn.Entity) + switch result { + case endpoints.Denied: + conn.Block("bypass prevention: " + reason) + return + case endpoints.Permitted: + conn.Accept("bypass prevention: " + reason) + return + case endpoints.NoMatch: + } + // check endpoints list - var result endpoints.EPResult - var reason string if conn.Inbound { result, reason = p.MatchServiceEndpoint(conn.Entity) } else { diff --git a/intel/entity.go b/intel/entity.go index f4412bc3..e667f07d 100644 --- a/intel/entity.go +++ b/intel/entity.go @@ -151,7 +151,6 @@ func (e *Entity) getLocation() { e.fetchLocationOnce.Do(func() { // need IP! if e.IP == nil { - log.Warningf("intel: cannot get location for %s data without IP", e.Domain) return } diff --git a/profile/config.go b/profile/config.go index 9cc341f9..bf0313a0 100644 --- a/profile/config.go +++ b/profile/config.go @@ -53,6 +53,9 @@ var ( CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS" cfgOptionRemoveBlockedDNS config.IntOption // security level option + + CfgOptionBypassProtectionKey = "filter/preventBypassing" + cfgOptionBypassProtection config.IntOption // security level option ) func registerConfiguration() error { @@ -325,5 +328,22 @@ Examples: cfgOptionRemoveBlockedDNS = config.Concurrent.GetAsInt(CfgOptionRemoveBlockedDNSKey, int64(status.SecurityLevelsAll)) cfgIntOptions[CfgOptionRemoveBlockedDNSKey] = cfgOptionRemoveBlockedDNS + err = config.Register(&config.Option{ + Name: "Prevent Bypassing", + Key: CfgOptionBypassProtectionKey, + Description: "Prevent apps from bypassing the privacy filter:\n- Firefox: Disable DNS-over-HTTPs", + OptType: config.OptTypeInt, + ExpertiseLevel: config.ExpertiseLevelUser, + ReleaseLevel: config.ReleaseLevelBeta, + ExternalOptType: "security level", + DefaultValue: status.SecurityLevelsAll, + ValidationRegex: "^(7|6|4|0)", + }) + if err != nil { + return err + } + cfgOptionBypassProtection = config.Concurrent.GetAsInt((CfgOptionBypassProtectionKey), int64(status.SecurityLevelsAll)) + cfgIntOptions[CfgOptionBypassProtectionKey] = cfgOptionBypassProtection + return nil } diff --git a/profile/profile-layered.go b/profile/profile-layered.go index 90f0478b..0fca0bad 100644 --- a/profile/profile-layered.go +++ b/profile/profile-layered.go @@ -1,6 +1,7 @@ package profile import ( + "strings" "sync" "sync/atomic" @@ -43,6 +44,7 @@ type LayeredProfile struct { RemoveOutOfScopeDNS config.BoolOption RemoveBlockedDNS config.BoolOption FilterSubDomains config.BoolOption + PreventBypassing config.BoolOption } // NewLayeredProfile returns a new layered profile based on the given local profile. @@ -98,6 +100,10 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile { CfgOptionFilterSubDomainsKey, cfgOptionFilterSubDomains, ) + new.PreventBypassing = new.wrapSecurityLevelOption( + CfgOptionBypassProtectionKey, + cfgOptionBypassProtection, + ) // TODO: load linked profiles. @@ -224,7 +230,7 @@ func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (result end // MatchFilterLists matches the entity against the set of filter // lists. -func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (result endpoints.EPResult, reason string) { +func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPResult, string) { entity.ResolveSubDomainLists(lp.FilterSubDomains()) lookupMap, hasLists := entity.GetListsMap() @@ -253,6 +259,22 @@ func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (result endpoin return endpoints.NoMatch, "" } +// MatchBypassProtection checks if the entity should be denied or permitted +// based on some bypass protection checks. +func (lp *LayeredProfile) MatchBypassProtection(entity *intel.Entity) (endpoints.EPResult, string) { + if !lp.PreventBypassing() { + return endpoints.NoMatch, "" + } + + // Block firefox canary domain to disable DoH + if strings.ToLower(entity.Domain) == "use-application-dns.net." { + log.Warningf("bypass protection for firefox canary") + return endpoints.Denied, "Firefox canary domain" + } + + return endpoints.NoMatch, "" +} + // AddEndpoint adds an endpoint to the local endpoint list, saves the local profile and reloads the configuration. func (lp *LayeredProfile) AddEndpoint(newEntry string) { lp.localProfile.AddEndpoint(newEntry) From 58ad3eb88b17dd20294e16e13f13d9ebcf22529c Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Apr 2020 11:04:17 +0200 Subject: [PATCH 2/3] Switch filterlist debug logging to trace --- intel/entity.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/intel/entity.go b/intel/entity.go index e667f07d..290da215 100644 --- a/intel/entity.go +++ b/intel/entity.go @@ -223,11 +223,11 @@ func (e *Entity) getDomainLists() { var domains = []string{domain} if e.resolveSubDomainLists { domains = splitDomain(domain) - log.Debugf("intel: subdomain list resolving is enabled, checking %v", domains) + log.Tracef("intel: subdomain list resolving is enabled, checking %v", domains) } for _, d := range domains { - log.Debugf("intel: loading domain list for %s", d) + log.Tracef("intel: loading domain list for %s", d) list, err := filterlists.LookupDomain(d) if err != nil { log.Errorf("intel: failed to get domain blocklists for %s: %s", d, err) @@ -277,7 +277,7 @@ func (e *Entity) getASNLists() { return } - log.Debugf("intel: loading ASN list for %d", asn) + log.Tracef("intel: loading ASN list for %d", asn) e.loadAsnListOnce.Do(func() { list, err := filterlists.LookupASNString(fmt.Sprintf("%d", asn)) if err != nil { @@ -301,7 +301,7 @@ func (e *Entity) getCountryLists() { return } - log.Debugf("intel: loading country list for %s", country) + log.Tracef("intel: loading country list for %s", country) e.loadCoutryListOnce.Do(func() { list, err := filterlists.LookupCountry(country) if err != nil { @@ -334,7 +334,7 @@ func (e *Entity) getIPLists() { return } - log.Debugf("intel: loading IP list for %s", ip) + log.Tracef("intel: loading IP list for %s", ip) e.loadIPListOnce.Do(func() { list, err := filterlists.LookupIP(ip) From ea3e327c272ed21706a1e8eb6fee76a96ac7c274 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Apr 2020 11:52:53 +0200 Subject: [PATCH 3/3] Implement review changes --- firewall/bypassing.go | 19 +++++++++++++++++++ firewall/master.go | 25 +++++++++++++++---------- profile/config.go | 14 +++++++------- profile/profile-layered.go | 21 ++------------------- 4 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 firewall/bypassing.go diff --git a/firewall/bypassing.go b/firewall/bypassing.go new file mode 100644 index 00000000..ac3349f5 --- /dev/null +++ b/firewall/bypassing.go @@ -0,0 +1,19 @@ +package firewall + +import ( + "strings" + + "github.com/safing/portmaster/network" + "github.com/safing/portmaster/profile/endpoints" +) + +// PreventBypassing checks if the connection should be denied or permitted +// based on some bypass protection checks. +func PreventBypassing(conn *network.Connection) (endpoints.EPResult, string) { + // Block firefox canary domain to disable DoH + if strings.ToLower(conn.Entity.Domain) == "use-application-dns.net." { + return endpoints.Denied, "blocked canary domain to prevent enabling DNS-over-HTTPs" + } + + return endpoints.NoMatch, "" +} diff --git a/firewall/master.go b/firewall/master.go index 3dd9f8e8..f09ad644 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -141,16 +141,21 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint: } } - // check for bypass protection - result, reason := p.MatchBypassProtection(conn.Entity) - switch result { - case endpoints.Denied: - conn.Block("bypass prevention: " + reason) - return - case endpoints.Permitted: - conn.Accept("bypass prevention: " + reason) - return - case endpoints.NoMatch: + var result endpoints.EPResult + var reason string + + if p.PreventBypassing() { + // check for bypass protection + result, reason := PreventBypassing(conn) + switch result { + case endpoints.Denied: + conn.Block("bypass prevention: " + reason) + return + case endpoints.Permitted: + conn.Accept("bypass prevention: " + reason) + return + case endpoints.NoMatch: + } } // check endpoints list diff --git a/profile/config.go b/profile/config.go index bf0313a0..2607646a 100644 --- a/profile/config.go +++ b/profile/config.go @@ -54,8 +54,8 @@ var ( CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS" cfgOptionRemoveBlockedDNS config.IntOption // security level option - CfgOptionBypassProtectionKey = "filter/preventBypassing" - cfgOptionBypassProtection config.IntOption // security level option + CfgOptionPreventBypassingKey = "filter/preventBypassing" + cfgOptionPreventBypassing config.IntOption // security level option ) func registerConfiguration() error { @@ -330,20 +330,20 @@ Examples: err = config.Register(&config.Option{ Name: "Prevent Bypassing", - Key: CfgOptionBypassProtectionKey, - Description: "Prevent apps from bypassing the privacy filter:\n- Firefox: Disable DNS-over-HTTPs", + Key: CfgOptionPreventBypassingKey, + Description: "Prevent apps from bypassing the privacy filter: Firefox by disabling DNS-over-HTTPs", OptType: config.OptTypeInt, ExpertiseLevel: config.ExpertiseLevelUser, ReleaseLevel: config.ReleaseLevelBeta, ExternalOptType: "security level", DefaultValue: status.SecurityLevelsAll, - ValidationRegex: "^(7|6|4|0)", + ValidationRegex: "^(7|6|4)", }) if err != nil { return err } - cfgOptionBypassProtection = config.Concurrent.GetAsInt((CfgOptionBypassProtectionKey), int64(status.SecurityLevelsAll)) - cfgIntOptions[CfgOptionBypassProtectionKey] = cfgOptionBypassProtection + cfgOptionPreventBypassing = config.Concurrent.GetAsInt((CfgOptionPreventBypassingKey), int64(status.SecurityLevelsAll)) + cfgIntOptions[CfgOptionPreventBypassingKey] = cfgOptionPreventBypassing return nil } diff --git a/profile/profile-layered.go b/profile/profile-layered.go index 0fca0bad..f00dbfe7 100644 --- a/profile/profile-layered.go +++ b/profile/profile-layered.go @@ -1,7 +1,6 @@ package profile import ( - "strings" "sync" "sync/atomic" @@ -101,8 +100,8 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile { cfgOptionFilterSubDomains, ) new.PreventBypassing = new.wrapSecurityLevelOption( - CfgOptionBypassProtectionKey, - cfgOptionBypassProtection, + CfgOptionPreventBypassingKey, + cfgOptionPreventBypassing, ) // TODO: load linked profiles. @@ -259,22 +258,6 @@ func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPRe return endpoints.NoMatch, "" } -// MatchBypassProtection checks if the entity should be denied or permitted -// based on some bypass protection checks. -func (lp *LayeredProfile) MatchBypassProtection(entity *intel.Entity) (endpoints.EPResult, string) { - if !lp.PreventBypassing() { - return endpoints.NoMatch, "" - } - - // Block firefox canary domain to disable DoH - if strings.ToLower(entity.Domain) == "use-application-dns.net." { - log.Warningf("bypass protection for firefox canary") - return endpoints.Denied, "Firefox canary domain" - } - - return endpoints.NoMatch, "" -} - // AddEndpoint adds an endpoint to the local endpoint list, saves the local profile and reloads the configuration. func (lp *LayeredProfile) AddEndpoint(newEntry string) { lp.localProfile.AddEndpoint(newEntry)