From 29bfa9fd916af5d1c3428a80a4d8caeb6c9f2506 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 15 Apr 2022 13:05:24 +0200 Subject: [PATCH 1/7] Add config option to disable dns query interception --- firewall/config.go | 22 ++++++++++++++++++++++ firewall/filter.go | 2 -- firewall/interception.go | 9 ++++----- go.mod | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/firewall/config.go b/firewall/config.go index 2e283a49..702b6c95 100644 --- a/firewall/config.go +++ b/firewall/config.go @@ -23,6 +23,10 @@ var ( cfgOptionPermanentVerdictsOrder = 96 permanentVerdicts config.BoolOption + CfgOptionDNSQueryInterceptionKey = "filter/dnsQueryInterception" + cfgOptionDNSQueryInterceptionOrder = 97 + dnsQueryInterception config.BoolOption + devMode config.BoolOption apiListenAddress config.StringOption ) @@ -46,6 +50,24 @@ func registerConfig() error { } permanentVerdicts = config.Concurrent.GetAsBool(CfgOptionPermanentVerdictsKey, true) + err = config.Register(&config.Option{ + Name: "Seamless DNS Integration", + Key: CfgOptionDNSQueryInterceptionKey, + Description: "Intercept and redirect astray DNS queries to the Portmaster's internal DNS server. This enables seamless DNS integration without having to configure the system or other software. However, this may lead to compatibility issues with other software that attempts the same.", + OptType: config.OptTypeBool, + ExpertiseLevel: config.ExpertiseLevelDeveloper, + ReleaseLevel: config.ReleaseLevelExperimental, + DefaultValue: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: cfgOptionDNSQueryInterceptionOrder, + config.CategoryAnnotation: "Advanced", + }, + }) + if err != nil { + return err + } + dnsQueryInterception = config.Concurrent.GetAsBool(CfgOptionDNSQueryInterceptionKey, true) + err = config.Register(&config.Option{ Name: "Prompt Desktop Notifications", Key: CfgOptionAskWithSystemNotificationsKey, diff --git a/firewall/filter.go b/firewall/filter.go index cb7dc3aa..58a24737 100644 --- a/firewall/filter.go +++ b/firewall/filter.go @@ -4,8 +4,6 @@ import ( "github.com/safing/portbase/config" "github.com/safing/portbase/modules" "github.com/safing/portbase/modules/subsystems" - - // Dependency. _ "github.com/safing/portmaster/core" "github.com/safing/spn/captain" ) diff --git a/firewall/interception.go b/firewall/interception.go index 25e0354b..ed582ba1 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -16,8 +16,6 @@ import ( "github.com/safing/portbase/log" "github.com/safing/portbase/modules" "github.com/safing/portmaster/compat" - - // Dependency. _ "github.com/safing/portmaster/core/base" "github.com/safing/portmaster/firewall/inspection" "github.com/safing/portmaster/firewall/interception" @@ -332,8 +330,9 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true - // Redirect outbound DNS packests, - case pkt.IsOutbound() && + // Redirect outbound DNS packets if enabled, + case dnsQueryInterception() && + pkt.IsOutbound() && pkt.Info().DstPort == 53 && // that don't match the address of our nameserver, nameserverIPMatcherReady.IsSet() && @@ -341,7 +340,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { // and are not broadcast queries by us. // Context: // - Unicast queries by the resolver are pre-authenticated. - // - Unicast qeries by the compat self-check should be redirected. + // - Unicast queries by the compat self-check should be redirected. !(conn.Process().Pid == ownPID && conn.Entity.IPScope == netutils.LocalMulticast): diff --git a/go.mod b/go.mod index cff7a842..b128b856 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/safing/portmaster -go 1.15 +go 1.18 require ( github.com/agext/levenshtein v1.2.3 From f5afe8b5dfa1daa1e9e95bc51190f598cf4b4937 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 15 Apr 2022 13:06:13 +0200 Subject: [PATCH 2/7] Block DNS requests if bypass prevention is active --- firewall/bypassing.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/firewall/bypassing.go b/firewall/bypassing.go index 4f6b0f1d..cf8502cb 100644 --- a/firewall/bypassing.go +++ b/firewall/bypassing.go @@ -43,8 +43,12 @@ func PreventBypassing(ctx context.Context, conn *network.Connection) (endpoints. return endpoints.NoMatch, "", nil } - // Block bypass attempts using an encrypted DNS server. + // Block bypass attempts using an (encrypted) DNS server. switch { + case conn.Entity.Port == 53: + return endpoints.Denied, + "blocked DNS query, manual dns setup required", + nsutil.BlockIP() case conn.Entity.Port == 853: // Block connections to port 853 - DNS over TLS. fallthrough From 2c3def3bc43414e82e78d1500c97f0c99b575c89 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 15 Apr 2022 13:06:59 +0200 Subject: [PATCH 3/7] Show notification about manual DNS setup instead of compatibility notice --- compat/module.go | 14 +++++-- compat/notify.go | 90 +++++++++++++++++++++++++++++++++------- compat/selfcheck.go | 16 +++---- nameserver/module.go | 4 ++ nameserver/nameserver.go | 2 +- 5 files changed, 98 insertions(+), 28 deletions(-) diff --git a/compat/module.go b/compat/module.go index 1517f2cc..d1a27628 100644 --- a/compat/module.go +++ b/compat/module.go @@ -16,7 +16,7 @@ var ( module *modules.Module selfcheckTask *modules.Task - selfcheckTaskRetryAfter = 10 * time.Second + selfcheckTaskRetryAfter = 5 * time.Second // selfCheckIsFailing holds whether or not the self-check is currently // failing. This helps other failure systems to not make noise when there is @@ -47,6 +47,8 @@ func prep() error { } func start() error { + startNotify() + selfcheckTask = module.NewTask("compatibility self-check", selfcheckTaskFunc). Repeat(5 * time.Minute). MaxDelay(selfcheckTaskRetryAfter). @@ -74,12 +76,18 @@ func stop() error { } func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error { + // Create tracing logger. + ctx, tracer := log.AddTracer(ctx) + defer tracer.Submit() + tracer.Tracef("compat: running self-check") + // Run selfcheck and return if successful. issue, err := selfcheck(ctx) if err == nil { selfCheckIsFailing.UnSet() selfcheckFails = 0 resetSystemIssue() + tracer.Debugf("compat: self-check successful") return nil } @@ -88,7 +96,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error { selfCheckIsFailing.Set() selfcheckFails++ - log.Errorf("compat: %s", err) + tracer.Errorf("compat: %s", err) if selfcheckFails >= selfcheckFailThreshold { issue.notify(err) } @@ -100,7 +108,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error { selfcheckFails = 0 // Only log internal errors, but don't notify. - log.Warningf("compat: %s", err) + tracer.Warningf("compat: %s", err) } return nil diff --git a/compat/notify.go b/compat/notify.go index 04beeff7..b483ead5 100644 --- a/compat/notify.go +++ b/compat/notify.go @@ -3,10 +3,12 @@ package compat import ( "context" "fmt" + "net" "strings" "sync" "time" + "github.com/safing/portbase/config" "github.com/safing/portbase/log" "github.com/safing/portbase/modules" "github.com/safing/portbase/notifications" @@ -15,10 +17,11 @@ import ( ) type baseIssue struct { - id string //nolint:structcheck // Inherited. - title string //nolint:structcheck // Inherited. - message string //nolint:structcheck // Inherited. - level notifications.Type //nolint:structcheck // Inherited. + id string //nolint:structcheck // Inherited. + title string //nolint:structcheck // Inherited. + message string //nolint:structcheck // Inherited. + level notifications.Type //nolint:structcheck // Inherited. + actions []*notifications.Action //nolint:structcheck // Inherited. } type systemIssue baseIssue @@ -26,6 +29,10 @@ type systemIssue baseIssue type appIssue baseIssue var ( + // Copy of firewall.CfgOptionDNSQueryInterceptionKey. + cfgOptionDNSQueryInterceptionKey = "filter/dnsQueryInterception" + dnsQueryInterception config.BoolOption + systemIssueNotification *notifications.Notification systemIssueNotificationLock sync.Mutex @@ -41,6 +48,22 @@ var ( message: "Portmaster detected that something is interfering with its operation. This could be a VPN, an Anti-Virus or another network protection software. Please check if you are running an incompatible [VPN client](https://docs.safing.io/portmaster/install/status/vpn-compatibility) or [software](https://docs.safing.io/portmaster/install/status/software-compatibility). Otherwise, please report the issue via [GitHub](https://github.com/safing/portmaster/issues) or send a mail to [support@safing.io](mailto:support@safing.io) so we can help you out.", level: notifications.Error, } + // manualDNSSetupRequired is additionally initialized in startNotify(). + manualDNSSetupRequired = &systemIssue{ + id: "compat:manual-dns-setup-required", + title: "Manual DNS Setup Required", + level: notifications.Error, + actions: []*notifications.Action{ + { + Text: "Revert", + Type: notifications.ActionTypeOpenSetting, + Payload: ¬ifications.ActionTypeOpenSettingPayload{ + Key: cfgOptionDNSQueryInterceptionKey, + }, + }, + }, + } + manualDNSSetupRequiredMessage = "You have disabled Seamless DNS Integration. As a result, Portmaster can no longer protect you or filter connections reliably. To fix this, you have to manually configure %s as the DNS Server in your system and in any conflicting application. This message will disappear 10 seconds after correct configuration." secureDNSBypassIssue = &appIssue{ id: "compat:secure-dns-bypass-%s", @@ -58,6 +81,37 @@ var ( } ) +func startNotify() { + dnsQueryInterception = config.Concurrent.GetAsBool(cfgOptionDNSQueryInterceptionKey, true) + + systemIssueNotificationLock.Lock() + defer systemIssueNotificationLock.Unlock() + + manualDNSSetupRequired.message = fmt.Sprintf( + manualDNSSetupRequiredMessage, + `"127.0.0.1"`, + ) +} + +// SetNameserverListenIP sets the IP address the nameserver is listening on. +// The IP address is used in compatibility notifications. +func SetNameserverListenIP(ip net.IP) { + systemIssueNotificationLock.Lock() + defer systemIssueNotificationLock.Unlock() + + manualDNSSetupRequired.message = fmt.Sprintf( + manualDNSSetupRequiredMessage, + `"`+ip.String()+`"`, + ) +} + +func systemCompatOrManualDNSIssue() *systemIssue { + if dnsQueryInterception() { + return systemCompatibilityIssue + } + return manualDNSSetupRequired +} + func (issue *systemIssue) notify(err error) { systemIssueNotificationLock.Lock() defer systemIssueNotificationLock.Unlock() @@ -74,11 +128,12 @@ func (issue *systemIssue) notify(err error) { // Create new notification. n := ¬ifications.Notification{ - EventID: issue.id, - Type: issue.level, - Title: issue.title, - Message: issue.message, - ShowOnSystem: true, + EventID: issue.id, + Type: issue.level, + Title: issue.title, + Message: issue.message, + ShowOnSystem: true, + AvailableActions: issue.actions, } notifications.Notify(n) @@ -141,17 +196,20 @@ func (issue *appIssue) notify(proc *process.Process) { // Create a new notification. n = ¬ifications.Notification{ - EventID: eventID, - Type: issue.level, - Title: fmt.Sprintf(issue.title, p.Name), - Message: message, - ShowOnSystem: true, - AvailableActions: []*notifications.Action{ + EventID: eventID, + Type: issue.level, + Title: fmt.Sprintf(issue.title, p.Name), + Message: message, + ShowOnSystem: true, + AvailableActions: issue.actions, + } + if len(n.AvailableActions) == 0 { + n.AvailableActions = []*notifications.Action{ { ID: "ack", Text: "OK", }, - }, + } } notifications.Notify(n) diff --git a/compat/selfcheck.go b/compat/selfcheck.go index 1931b70e..c1508d12 100644 --- a/compat/selfcheck.go +++ b/compat/selfcheck.go @@ -28,12 +28,12 @@ var ( systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol)) systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String() systemIntegrationCheckPackets = make(chan packet.Packet, 1) - systemIntegrationCheckWaitDuration = 30 * time.Second + systemIntegrationCheckWaitDuration = 20 * time.Second // DNSCheckInternalDomainScope is the domain scope to use for dns checks. DNSCheckInternalDomainScope = ".self-check." + resolver.InternalSpecialUseDomain dnsCheckReceivedDomain = make(chan string, 1) - dnsCheckWaitDuration = 30 * time.Second + dnsCheckWaitDuration = 20 * time.Second dnsCheckAnswerLock sync.Mutex dnsCheckAnswer net.IP ) @@ -61,7 +61,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) { if err != nil { return nil, fmt.Errorf("failed to create system integration conn: %w", err) } - _, err = conn.Write([]byte("SELF-CHECK")) + _, err = conn.Write([]byte("PORTMASTER SELF CHECK")) if err != nil { return nil, fmt.Errorf("failed to send system integration packet: %w", err) } @@ -70,7 +70,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) { select { case <-systemIntegrationCheckPackets: // Check passed! - log.Tracef("compat: self-check #1: system integration check passed") + log.Tracer(ctx).Tracef("compat: self-check #1: system integration check passed") case <-time.After(systemIntegrationCheckWaitDuration): return systemIntegrationIssue, fmt.Errorf("self-check #1: system integration check failed: did not receive test packet after %s", systemIntegrationCheckWaitDuration) case <-ctx.Done(): @@ -139,12 +139,12 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) { select { case receivedTestDomain := <-dnsCheckReceivedDomain: if receivedTestDomain != randomSubdomain { - return systemCompatibilityIssue, fmt.Errorf("self-check #2: dns integration check failed: received unmatching subdomain %q", receivedTestDomain) + return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: received unmatching subdomain %q", receivedTestDomain) } case <-time.After(dnsCheckWaitDuration): - return systemCompatibilityIssue, fmt.Errorf("self-check #2: dns integration check failed: did not receive test query after %s", dnsCheckWaitDuration) + return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: did not receive test query after %s", dnsCheckWaitDuration) } - log.Tracef("compat: self-check #2: dns integration query check passed") + log.Tracer(ctx).Tracef("compat: self-check #2: dns integration query check passed") // Step 3: Have the nameserver respond with random data in the answer section. @@ -164,7 +164,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) { if !dnsCheckReturnedIP.Equal(randomAnswer) { return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: received unmatching response %q", dnsCheckReturnedIP) } - log.Tracef("compat: self-check #3: dns integration response check passed") + log.Tracer(ctx).Tracef("compat: self-check #3: dns integration response check passed") return nil, nil } diff --git a/nameserver/module.go b/nameserver/module.go index 7bc177e4..1c1dc871 100644 --- a/nameserver/module.go +++ b/nameserver/module.go @@ -13,6 +13,7 @@ import ( "github.com/safing/portbase/log" "github.com/safing/portbase/modules" "github.com/safing/portbase/modules/subsystems" + "github.com/safing/portmaster/compat" "github.com/safing/portmaster/firewall" "github.com/safing/portmaster/netenv" ) @@ -53,6 +54,9 @@ func start() error { return fmt.Errorf("failed to parse nameserver listen address: %w", err) } + // Tell the compat module where we are listening. + compat.SetNameserverListenIP(ip1) + // Get own hostname. hostname, err = os.Hostname() if err != nil { diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index 4c5bf114..a92ca8ad 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -168,7 +168,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) } default: - tracer.Warningf("nameserver: external request for %s%s, ignoring", q.FQDN, q.QType) + tracer.Warningf("nameserver: external request from %s for %s%s, ignoring", remoteAddr, q.FQDN, q.QType) return reply(nsutil.Refused("external queries are not permitted")) } conn.Lock() From 42eb3a1d0e322d035cc0d8fe37f42eeb22cf99d2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 15 Apr 2022 13:07:13 +0200 Subject: [PATCH 4/7] Use more verbose names for iptables chains --- firewall/interception/nfqueue_linux.go | 110 +++++++++++++------------ 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/firewall/interception/nfqueue_linux.go b/firewall/interception/nfqueue_linux.go index 0b207e41..f117cd67 100644 --- a/firewall/interception/nfqueue_linux.go +++ b/firewall/interception/nfqueue_linux.go @@ -46,83 +46,89 @@ type nfQueue interface { func init() { v4chains = []string{ - "mangle C170", - "mangle C171", - "filter C17", + "mangle PORTMASTER-INGEST-OUTPUT", + "mangle PORTMASTER-INGEST-INPUT", + "filter PORTMASTER-FILTER", + "nat PORTMASTER-REDIRECT", } v4rules = []string{ - "mangle C170 -j CONNMARK --restore-mark", - "mangle C170 -m mark --mark 0 -j NFQUEUE --queue-num 17040 --queue-bypass", + "mangle PORTMASTER-INGEST-OUTPUT -j CONNMARK --restore-mark", + "mangle PORTMASTER-INGEST-OUTPUT -m mark --mark 0 -j NFQUEUE --queue-num 17040 --queue-bypass", - "mangle C171 -j CONNMARK --restore-mark", - "mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass", + "mangle PORTMASTER-INGEST-INPUT -j CONNMARK --restore-mark", + "mangle PORTMASTER-INGEST-INPUT -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass", - "filter C17 -m mark --mark 0 -j DROP", - "filter C17 -m mark --mark 1700 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 0 -j DROP", + "filter PORTMASTER-FILTER -m mark --mark 1700 -j RETURN", // Accepting ICMP packets with mark 1701 is required for rejecting to work, // as the rejection ICMP packet will have the same mark. Blocked ICMP // packets will always result in a drop within the Portmaster. - "filter C17 -m mark --mark 1701 -p icmp -j RETURN", - "filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp-host-prohibited", - "filter C17 -m mark --mark 1702 -j DROP", - "filter C17 -j CONNMARK --save-mark", - "filter C17 -m mark --mark 1710 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1701 -p icmp -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1701 -j REJECT --reject-with icmp-host-prohibited", + "filter PORTMASTER-FILTER -m mark --mark 1702 -j DROP", + "filter PORTMASTER-FILTER -j CONNMARK --save-mark", + "filter PORTMASTER-FILTER -m mark --mark 1710 -j RETURN", // Accepting ICMP packets with mark 1711 is required for rejecting to work, // as the rejection ICMP packet will have the same mark. Blocked ICMP // packets will always result in a drop within the Portmaster. - "filter C17 -m mark --mark 1711 -p icmp -j RETURN", - "filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp-host-prohibited", - "filter C17 -m mark --mark 1712 -j DROP", - "filter C17 -m mark --mark 1717 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1711 -p icmp -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1711 -j REJECT --reject-with icmp-host-prohibited", + "filter PORTMASTER-FILTER -m mark --mark 1712 -j DROP", + "filter PORTMASTER-FILTER -m mark --mark 1717 -j RETURN", + + "nat PORTMASTER-REDIRECT -m mark --mark 1799 -p udp -j DNAT --to 127.0.0.17:53", + "nat PORTMASTER-REDIRECT -m mark --mark 1717 -p tcp -j DNAT --to 127.0.0.17:717", + "nat PORTMASTER-REDIRECT -m mark --mark 1717 -p udp -j DNAT --to 127.0.0.17:717", + // "nat PORTMASTER-REDIRECT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to 127.0.0.17", } v4once = []string{ - "mangle OUTPUT -j C170", - "mangle INPUT -j C171", - "filter OUTPUT -j C17", - "filter INPUT -j C17", - "nat OUTPUT -m mark --mark 1799 -p udp -j DNAT --to 127.0.0.17:53", - "nat OUTPUT -m mark --mark 1717 -p tcp -j DNAT --to 127.0.0.17:717", - "nat OUTPUT -m mark --mark 1717 -p udp -j DNAT --to 127.0.0.17:717", - // "nat OUTPUT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to 127.0.0.17", + "mangle OUTPUT -j PORTMASTER-INGEST-OUTPUT", + "mangle INPUT -j PORTMASTER-INGEST-INPUT", + "filter OUTPUT -j PORTMASTER-FILTER", + "filter INPUT -j PORTMASTER-FILTER", + "nat OUTPUT -j PORTMASTER-REDIRECT", } v6chains = []string{ - "mangle C170", - "mangle C171", - "filter C17", + "mangle PORTMASTER-INGEST-OUTPUT", + "mangle PORTMASTER-INGEST-INPUT", + "filter PORTMASTER-FILTER", + "nat PORTMASTER-REDIRECT", } v6rules = []string{ - "mangle C170 -j CONNMARK --restore-mark", - "mangle C170 -m mark --mark 0 -j NFQUEUE --queue-num 17060 --queue-bypass", + "mangle PORTMASTER-INGEST-OUTPUT -j CONNMARK --restore-mark", + "mangle PORTMASTER-INGEST-OUTPUT -m mark --mark 0 -j NFQUEUE --queue-num 17060 --queue-bypass", - "mangle C171 -j CONNMARK --restore-mark", - "mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass", + "mangle PORTMASTER-INGEST-INPUT -j CONNMARK --restore-mark", + "mangle PORTMASTER-INGEST-INPUT -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass", - "filter C17 -m mark --mark 0 -j DROP", - "filter C17 -m mark --mark 1700 -j RETURN", - "filter C17 -m mark --mark 1701 -p icmpv6 -j RETURN", - "filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited", - "filter C17 -m mark --mark 1702 -j DROP", - "filter C17 -j CONNMARK --save-mark", - "filter C17 -m mark --mark 1710 -j RETURN", - "filter C17 -m mark --mark 1711 -p icmpv6 -j RETURN", - "filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited", - "filter C17 -m mark --mark 1712 -j DROP", - "filter C17 -m mark --mark 1717 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 0 -j DROP", + "filter PORTMASTER-FILTER -m mark --mark 1700 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1701 -p icmpv6 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited", + "filter PORTMASTER-FILTER -m mark --mark 1702 -j DROP", + "filter PORTMASTER-FILTER -j CONNMARK --save-mark", + "filter PORTMASTER-FILTER -m mark --mark 1710 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1711 -p icmpv6 -j RETURN", + "filter PORTMASTER-FILTER -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited", + "filter PORTMASTER-FILTER -m mark --mark 1712 -j DROP", + "filter PORTMASTER-FILTER -m mark --mark 1717 -j RETURN", + + "nat PORTMASTER-REDIRECT -m mark --mark 1799 -p udp -j DNAT --to [::1]:53", + "nat PORTMASTER-REDIRECT -m mark --mark 1717 -p tcp -j DNAT --to [::1]:717", + "nat PORTMASTER-REDIRECT -m mark --mark 1717 -p udp -j DNAT --to [::1]:717", + // "nat PORTMASTER-REDIRECT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to [::1]", } v6once = []string{ - "mangle OUTPUT -j C170", - "mangle INPUT -j C171", - "filter OUTPUT -j C17", - "filter INPUT -j C17", - "nat OUTPUT -m mark --mark 1799 -p udp -j DNAT --to [::1]:53", - "nat OUTPUT -m mark --mark 1717 -p tcp -j DNAT --to [::1]:717", - "nat OUTPUT -m mark --mark 1717 -p udp -j DNAT --to [::1]:717", - // "nat OUTPUT -m mark --mark 1717 ! -p tcp ! -p udp -j DNAT --to [::1]", + "mangle OUTPUT -j PORTMASTER-INGEST-OUTPUT", + "mangle INPUT -j PORTMASTER-INGEST-INPUT", + "filter OUTPUT -j PORTMASTER-FILTER", + "filter INPUT -j PORTMASTER-FILTER", + "nat OUTPUT -j PORTMASTER-REDIRECT", } // Reverse because we'd like to insert in a loop From 9a39caf22bc37b29581370de3d62cbd5f1f1c953 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Apr 2022 15:02:05 +0200 Subject: [PATCH 5/7] Resolve to real file paths before checking path based API access --- firewall/api.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/firewall/api.go b/firewall/api.go index 18c675e9..99ae36b5 100644 --- a/firewall/api.go +++ b/firewall/api.go @@ -22,11 +22,7 @@ import ( ) const ( - deniedMsgUnidentified = `%wFailed to identify the requesting process. -You can enable the Development Mode to disable API authentication for development purposes. - -If you are seeing this message in the Portmaster App, please restart the app or right-click and select "Reload". -In the future, this issue will be remediated automatically.` + deniedMsgUnidentified = `%wFailed to identify the requesting process. Reload to try again.` deniedMsgSystem = `%wSystem access to the Portmaster API is not permitted. You can enable the Development Mode to disable API authentication for development purposes.` @@ -136,6 +132,12 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo if authenticatedPath == "" { return false, fmt.Errorf(deniedMsgMisconfigured, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user } + // Get real path. + authenticatedPath, err = filepath.EvalSymlinks(authenticatedPath) + if err != nil { + return false, fmt.Errorf(deniedMsgUnidentified, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user + } + // Add filepath separator to confine to directory. authenticatedPath += string(filepath.Separator) // Get process of request. @@ -157,8 +159,10 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo break checkLevelsLoop default: // normal process // Check if the requesting process is in database root / updates dir. - if strings.HasPrefix(proc.Path, authenticatedPath) { - return false, nil + if realPath, err := filepath.EvalSymlinks(proc.Path); err == nil { + if strings.HasPrefix(realPath, authenticatedPath) { + return false, nil + } } } From d0e3107ea52af81ba7a8388fe61300e71f5f9a90 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Apr 2022 15:02:39 +0200 Subject: [PATCH 6/7] Only warn about unexpected parent process when not in dev mode --- updates/upgrader.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/updates/upgrader.go b/updates/upgrader.go index aabc2441..ebab6870 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -230,7 +230,10 @@ func warnOnIncorrectParentPath() { return } if parentName != expectedFileName { - log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName) + // Only warn about this if not in dev mode. + if !devMode() { + log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName) + } // TODO(ppacher): once we released a new installer and folks had time // to update we should send a module warning/hint to the From 998662d928523dfaf621c43d83cc256579819ee6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Apr 2022 15:03:01 +0200 Subject: [PATCH 7/7] Add MatchMulti function to match endpoints list against multiple entities --- profile/endpoints/endpoints.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/profile/endpoints/endpoints.go b/profile/endpoints/endpoints.go index 05273f95..6ed3ad04 100644 --- a/profile/endpoints/endpoints.go +++ b/profile/endpoints/endpoints.go @@ -90,7 +90,32 @@ func (e Endpoints) IsSet() bool { // Match checks whether the given entity matches any of the endpoint definitions in the list. func (e Endpoints) Match(ctx context.Context, entity *intel.Entity) (result EPResult, reason Reason) { for _, entry := range e { - if entry != nil { + if entry == nil { + continue + } + + if result, reason = entry.Matches(ctx, entity); result != NoMatch { + return + } + } + + return NoMatch, nil +} + +// MatchMulti checks whether the given entities match any of the endpoint +// definitions in the list. Every rule is evaluated against all given entities +// and only if not match was registered, the next rule is evaluated. +func (e Endpoints) MatchMulti(ctx context.Context, entities ...*intel.Entity) (result EPResult, reason Reason) { + for _, entry := range e { + if entry == nil { + continue + } + + for _, entity := range entities { + if entity == nil { + continue + } + if result, reason = entry.Matches(ctx, entity); result != NoMatch { return }