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 9650b625..f09ad644 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -141,9 +141,24 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint: } } - // check endpoints list 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 if conn.Inbound { result, reason = p.MatchServiceEndpoint(conn.Entity) } else { diff --git a/intel/entity.go b/intel/entity.go index f4412bc3..290da215 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 } @@ -224,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) @@ -278,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 { @@ -302,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 { @@ -335,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) diff --git a/profile/config.go b/profile/config.go index 9cc341f9..2607646a 100644 --- a/profile/config.go +++ b/profile/config.go @@ -53,6 +53,9 @@ var ( CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS" cfgOptionRemoveBlockedDNS config.IntOption // security level option + + CfgOptionPreventBypassingKey = "filter/preventBypassing" + cfgOptionPreventBypassing 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: 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)", + }) + if err != nil { + return err + } + 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 90f0478b..f00dbfe7 100644 --- a/profile/profile-layered.go +++ b/profile/profile-layered.go @@ -43,6 +43,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 +99,10 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile { CfgOptionFilterSubDomainsKey, cfgOptionFilterSubDomains, ) + new.PreventBypassing = new.wrapSecurityLevelOption( + CfgOptionPreventBypassingKey, + cfgOptionPreventBypassing, + ) // TODO: load linked profiles. @@ -224,7 +229,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()