Add special handling to dns queries from the system resolver

This commit is contained in:
Daniel
2021-03-20 23:12:46 +01:00
parent a38f546da8
commit 01e7160bfe
5 changed files with 129 additions and 75 deletions

View File

@@ -16,7 +16,7 @@ import (
"github.com/safing/portmaster/resolver"
)
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ([]dns.RR, []string, int, string) {
func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, resolverScope netutils.IPScope, sysResolver bool) ([]dns.RR, []string, int, string) {
goodEntries := make([]dns.RR, 0, len(entries))
filteredRecords := make([]string, 0, len(entries))
@@ -38,16 +38,16 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
goodEntries = append(goodEntries, rr)
continue
}
classification := netutils.ClassifyIP(ip)
ipScope := netutils.GetIPScope(ip)
if p.RemoveOutOfScopeDNS() {
switch {
case classification == netutils.HostLocal:
case ipScope.IsLocalhost():
// No DNS should return localhost addresses
filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
continue
case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
case resolverScope.IsGlobal() && ipScope.IsLAN() && !sysResolver:
// No global DNS should return LAN addresses
filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
@@ -55,18 +55,18 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
}
}
if p.RemoveBlockedDNS() {
if p.RemoveBlockedDNS() && !sysResolver {
// filter by flags
switch {
case p.BlockScopeInternet() && classification == netutils.Global:
case p.BlockScopeInternet() && ipScope.IsGlobal():
filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeInternetKey
continue
case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
case p.BlockScopeLAN() && ipScope.IsLAN():
filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeLANKey
continue
case p.BlockScopeLocal() && classification == netutils.HostLocal:
case p.BlockScopeLocal() && ipScope.IsLocalhost():
filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeLocalKey
continue
@@ -83,7 +83,7 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey
}
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *resolver.RRCache {
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache, sysResolver bool) *resolver.RRCache {
p := conn.Process().Profile()
// do not modify own queries
@@ -104,11 +104,11 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *res
var validIPs int
var interveningOptionKey string
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.ServerScope)
rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.Resolver.IPScope, sysResolver)
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
// we don't count the valid IPs in the extra section
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.ServerScope)
rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.Resolver.IPScope, sysResolver)
rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
if len(rrCache.FilteredEntries) > 0 {
@@ -160,8 +160,9 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *res
return rrCache
}
// DecideOnResolvedDNS filters a dns response according to the application profile and settings.
func DecideOnResolvedDNS(
// FilterResolvedDNS filters a dns response according to the application
// profile and settings.
func FilterResolvedDNS(
ctx context.Context,
conn *network.Connection,
q *resolver.Query,
@@ -174,14 +175,15 @@ func DecideOnResolvedDNS(
return rrCache
}
updatedRR := filterDNSResponse(conn, rrCache)
// Only filter criticial things if request comes from the system resolver.
sysResolver := conn.Process().IsSystemResolver()
updatedRR := filterDNSResponse(conn, rrCache, sysResolver)
if updatedRR == nil {
return nil
}
updateIPsAndCNAMEs(q, rrCache, conn)
if mayBlockCNAMEs(ctx, conn) {
if !sysResolver && mayBlockCNAMEs(ctx, conn) {
return nil
}
@@ -213,14 +215,23 @@ func mayBlockCNAMEs(ctx context.Context, conn *network.Connection) bool {
return false
}
// updateIPsAndCNAMEs saves all the IP->Name mappings to the cache database and
// UpdateIPsAndCNAMEs saves all the IP->Name mappings to the cache database and
// updates the CNAMEs in the Connection's Entity.
func updateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *network.Connection) {
func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *network.Connection) {
// Sanity check input, as this is called from defer.
if q == nil || rrCache == nil {
return
}
// Get profileID for scoping IPInfo.
var profileID string
proc := conn.Process()
if proc != nil {
profileID = proc.LocalProfileKey
localProfile := conn.Process().Profile().LocalProfile()
switch localProfile.ID {
case profile.UnidentifiedProfileID,
profile.SystemResolverProfileID:
profileID = resolver.IPInfoProfileScopeGlobal
default:
profileID = localProfile.ID
}
// Collect IPs and CNAMEs.
@@ -249,8 +260,9 @@ func updateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw
// Create new record for this IP.
record := resolver.ResolvedDomain{
Domain: q.FQDN,
Expires: rrCache.Expires,
Domain: q.FQDN,
Expires: rrCache.Expires,
Resolver: rrCache.Resolver,
}
// Resolve all CNAMEs in the correct order and add the to the record.

View File

@@ -39,7 +39,7 @@ const noReasonOptionKey = ""
type deciderFn func(context.Context, *network.Connection, packet.Packet) bool
var deciders = []deciderFn{
var defaultDeciders = []deciderFn{
checkPortmasterConnection,
checkSelfCommunication,
checkConnectionType,
@@ -53,6 +53,11 @@ var deciders = []deciderFn{
checkAutoPermitRelated,
}
var dnsFromSystemResolverDeciders = []deciderFn{
checkConnectivityDomain,
checkBypassPrevention,
}
// 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) {
@@ -79,8 +84,21 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
}
}
// 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, pkt)
if !done {
conn.Accept("permitting system resolver dns request", noReasonOptionKey)
}
return
}
// Run all deciders and return if they came to a conclusion.
done, defaultAction := runDeciders(ctx, conn, pkt)
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, pkt)
if done {
return
}
@@ -96,7 +114,7 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
}
}
func runDeciders(ctx context.Context, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) {
func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) {
layeredProfile := conn.Process().Profile()
// Read-lock the all the profiles.
@@ -104,7 +122,7 @@ func runDeciders(ctx context.Context, conn *network.Connection, pkt packet.Packe
defer layeredProfile.UnlockForUsage()
// Go though all deciders, return if one sets an action.
for _, decider := range deciders {
for _, decider := range selectedDeciders {
if decider(ctx, conn, pkt) {
return true, profile.DefaultActionNotSet
}
@@ -248,39 +266,58 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
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", profile.CfgOptionBlockScopeInternetKey) // Block Outbound / Drop Inbound
return true
}
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
if p.BlockScopeLAN() {
conn.Block("LAN access blocked", profile.CfgOptionBlockScopeLANKey) // Block Outbound / Drop Inbound
return true
}
case netutils.HostLocal:
if p.BlockScopeLocal() {
conn.Block("Localhost access blocked", profile.CfgOptionBlockScopeLocalKey) // Block Outbound / Drop Inbound
return true
}
default: // netutils.Invalid
conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound
return true
}
} else if conn.Entity.Domain != "" {
// This is a DNS Request.
// If we are handling a DNS request, check if we can immediately block it.
if conn.Type == network.DNSRequest {
// DNS is expected to resolve to LAN or Internet addresses.
// Localhost queries are immediately responded to by the nameserver.
if p.BlockScopeInternet() && p.BlockScopeLAN() {
conn.Block("Internet and LAN access blocked", profile.CfgOptionBlockScopeInternetKey)
return true
}
return false
}
// Check if the network scope is permitted.
switch conn.Entity.IPScope {
case netutils.Global, netutils.GlobalMulticast:
if p.BlockScopeInternet() {
conn.Deny("Internet access blocked", profile.CfgOptionBlockScopeInternetKey) // Block Outbound / Drop Inbound
return true
}
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
if p.BlockScopeLAN() {
conn.Block("LAN access blocked", profile.CfgOptionBlockScopeLANKey) // Block Outbound / Drop Inbound
return true
}
case netutils.HostLocal:
if p.BlockScopeLocal() {
conn.Block("Localhost access blocked", profile.CfgOptionBlockScopeLocalKey) // Block Outbound / Drop Inbound
return true
}
default: // netutils.Unknown and netutils.Invalid
conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound
return true
}
// If the IP address was resolved, check the scope of the resolver.
switch {
case p.RemoveOutOfScopeDNS():
// Out of scope checking is not active.
case conn.Resolver == nil:
// IP address of connection was not resolved.
case conn.Resolver.IPScope.IsGlobal() &&
(conn.Entity.IPScope.IsLAN() || conn.Entity.IPScope.IsLocalhost()):
// Block global resolvers from returning LAN/Localhost IPs.
conn.Block("DNS server horizon violation: global DNS server returned local IP address", profile.CfgOptionRemoveOutOfScopeDNSKey)
return true
case conn.Resolver.IPScope.IsLAN() &&
conn.Entity.IPScope.IsLocalhost():
// Block LAN resolvers from returning Localhost IPs.
conn.Block("DNS server horizon violation: LAN DNS server returned localhost IP address", profile.CfgOptionRemoveOutOfScopeDNSKey)
return true
}
return false
}