Merge pull request #327 from safing/fix/patch-set-4

Improve system resolver and ICMP handling
This commit is contained in:
Patrick Pacher
2021-06-01 13:53:32 +02:00
committed by GitHub
8 changed files with 102 additions and 25 deletions

View File

@@ -9,6 +9,7 @@ import (
"sync/atomic"
"time"
"github.com/google/gopacket/layers"
"github.com/safing/portmaster/netenv"
"golang.org/x/sync/singleflight"
@@ -184,30 +185,39 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
}
switch meta.Protocol {
case packet.ICMP:
// Submit to ICMP listener.
submitted := netenv.SubmitPacketToICMPListener(pkt)
// Always permit ICMP.
log.Debugf("filter: fast-track accepting ICMP: %s", pkt)
// If the packet was submitted to the listener, we must not do a
// permanent accept, because then we won't see any future packets of that
// connection and thus cannot continue to submit them.
if submitted {
_ = pkt.Accept()
} else {
case packet.ICMP, packet.ICMPv6:
// Load packet data.
err := pkt.LoadPacketData()
if err != nil {
log.Debugf("filter: failed to load ICMP packet data: %s", err)
_ = pkt.PermanentAccept()
return true
}
return true
case packet.ICMPv6:
// Handle echo request and replies regularly.
// Other ICMP packets are considered system business.
icmpLayers := pkt.Layers().LayerClass(layers.LayerClassIPControl)
switch icmpLayer := icmpLayers.(type) {
case *layers.ICMPv4:
switch icmpLayer.TypeCode.Type() {
case layers.ICMPv4TypeEchoRequest,
layers.ICMPv4TypeEchoReply:
return false
}
case *layers.ICMPv6:
switch icmpLayer.TypeCode.Type() {
case layers.ICMPv6TypeEchoRequest,
layers.ICMPv6TypeEchoReply:
return false
}
}
// Premit all ICMP/v6 packets that are not echo requests or replies.
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
// Submit to ICMP listener.
submitted := netenv.SubmitPacketToICMPListener(pkt)
// Always permit ICMPv6.
log.Debugf("filter: fast-track accepting ICMPv6: %s", pkt)
// If the packet was submitted to the listener, we must not do a
// permanent accept, because then we won't see any future packets of that
// connection and thus cannot continue to submit them.

View File

@@ -145,7 +145,7 @@ func (pkt *packet) PermanentAccept() error {
}
func (pkt *packet) PermanentBlock() error {
if pkt.Info().Protocol == pmpacket.ICMP {
if pkt.Info().Protocol == pmpacket.ICMP || pkt.Info().Protocol == pmpacket.ICMPv6 {
// ICMP packets attributed to a blocked connection are always allowed, as
// rejection ICMP packets will have the same mark as the blocked
// connection. This is why we need to drop blocked ICMP packets instead.

View File

@@ -55,8 +55,10 @@ var defaultDeciders = []deciderFn{
}
var dnsFromSystemResolverDeciders = []deciderFn{
checkEndpointListsForSystemResolverDNSRequests,
checkConnectivityDomain,
checkBypassPrevention,
checkFilterLists,
}
// DecideOnConnection makes a decision about a connection.
@@ -214,6 +216,29 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil
return false
}
// checkEndpointListsForSystemResolverDNSRequests is a special version of
// 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 {
profileEndpoints := p.LocalProfile().GetEndpoints()
if profileEndpoints.IsSet() {
result, reason := profileEndpoints.Match(ctx, conn.Entity)
if endpoints.IsDecision(result) {
switch result {
case endpoints.Denied:
conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context())
return true
case endpoints.Permitted:
conn.AcceptWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context())
return true
}
}
}
return false
}
func checkConnectionType(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
switch {
case conn.Type != network.IPConnection:

View File

@@ -107,6 +107,6 @@ func warnAboutDisabledFilterLists() {
module.Warning(
filterlistsDisabled,
"Filter Lists Are Initializing",
"Filter lists are being downloaded and set up in the background. Until this initialization is finished, the filter lists are disabled and will not block anything.",
"Filter lists are being downloaded and set up in the background. They will be activated as configured when finished.",
)
}

View File

@@ -27,7 +27,7 @@ func GetProcessByConnection(ctx context.Context, pktInfo *packet.Info) (process
var pid int
pid, connInbound, err = state.Lookup(pktInfo, fastSearch)
if err != nil {
log.Tracer(ctx).Debugf("process: failed to find PID of connection: %s", err)
log.Tracer(ctx).Tracef("process: failed to find PID of connection: %s", err)
return nil, pktInfo.Inbound, err
}

View File

@@ -37,7 +37,7 @@ func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc
func (ep *EndpointBase) makeReason(s fmt.Stringer, value, desc string, keyval ...interface{}) Reason {
r := &reason{
description: desc,
Filter: ep.renderPPP(s.String()),
Filter: s.String(),
Permitted: ep.Permitted,
Value: value,
}

View File

@@ -301,6 +301,22 @@ func (profile *Profile) IsOutdated() bool {
return profile.outdated.IsSet()
}
// GetEndpoints returns the endpoint list of the profile.
func (profile *Profile) GetEndpoints() endpoints.Endpoints {
profile.RLock()
defer profile.RUnlock()
return profile.endpoints
}
// GetServiceEndpoints returns the service endpoint list of the profile.
func (profile *Profile) GetServiceEndpoints() endpoints.Endpoints {
profile.RLock()
defer profile.RUnlock()
return profile.serviceEndpoints
}
// AddEndpoint adds an endpoint to the endpoint list, saves the profile and reloads the configuration.
func (profile *Profile) AddEndpoint(newEntry string) {
profile.addEndpointyEntry(CfgOptionEndpointsKey, newEntry)

View File

@@ -85,19 +85,45 @@ func getSpecialProfile(profileID, linkedPath string) *Profile {
return New(SourceLocal, SystemProfileID, linkedPath, nil)
case SystemResolverProfileID:
return New(
systemResolverProfile := New(
SourceLocal,
SystemResolverProfileID,
linkedPath,
map[string]interface{}{
// Explicitly setting the default action to "permit" will improve the
// user experience for people who set the global default to "prompt".
// Resolved domain from the system resolver are checked again when
// attributed to a connection of a regular process. Otherwise, users
// would see two connection prompts for the same domain.
CfgOptionDefaultActionKey: "permit",
// Explicitly allow localhost and answers to multicast protocols that
// are commonly used by system resolvers.
// TODO: When the Portmaster gains the ability to attribute multicast
// responses to their requests, these rules can probably be removed
// again.
CfgOptionServiceEndpointsKey: []string{
"+ Localhost", // Allow everything from localhost.
"+ LAN UDP/5353", // Allow inbound mDNS requests and multicast replies.
"+ LAN UDP/5355", // Allow inbound LLMNR requests and multicast replies.
"+ LAN UDP/1900", // Allow inbound SSDP requests and multicast replies.
},
// Explicitly disable all filter lists, as these will be checked later
// with the attributed connection. As this is the system resolver, this
// list can instead be used as a global enforcement of filter lists, if
// the system resolver is used. Users who want to
CfgOptionFilterListsKey: []string{},
},
)
// Add description to tell users about the quirks of this profile.
systemResolverProfile.Description = `The System DNS Client is a system service that requires special handling. For regular network connections, the configured settings will apply as usual, but DNS requests coming from the System DNS Client are handled in a special way, as they could actually be coming from any other application on the system.
In order to respect the app settings of the actual application, DNS requests from the System DNS Client are only subject to the following settings:
- Outgoing Rules (without global rules)
- Block Bypassing
- Filter Lists
`
return systemResolverProfile
case PortmasterProfileID:
profile := New(SourceLocal, PortmasterProfileID, linkedPath, nil)
@@ -155,7 +181,7 @@ func specialProfileNeedsReset(profile *Profile) bool {
switch profile.ID {
case SystemResolverProfileID:
return canBeUpgraded(profile, "18.5.2021")
return canBeUpgraded(profile, "1.6.2021")
default:
// Not a special profile or no upgrade available yet.
return false
@@ -175,7 +201,7 @@ func canBeUpgraded(profile *Profile, upgradeDate string) bool {
log.Infof("profile: upgrading special profile %s", profile.ScopedID())
notifications.NotifyInfo(
"profiles:upgraded-special-profile-"+profile.ID,
"profiles:upgraded-special-profile:"+profile.ID,
profile.Name+" Settings Upgraded",
// TODO: Remove disclaimer.
fmt.Sprintf(