Merge pull request #938 from safing/fix/verdict-and-profile-issues
Fix verdict and profile issues
This commit is contained in:
14
core/api.go
14
core/api.go
@@ -25,9 +25,10 @@ import (
|
|||||||
|
|
||||||
func registerAPIEndpoints() error {
|
func registerAPIEndpoints() error {
|
||||||
if err := api.RegisterEndpoint(api.Endpoint{
|
if err := api.RegisterEndpoint(api.Endpoint{
|
||||||
Path: "core/shutdown",
|
Path: "core/shutdown",
|
||||||
Write: api.PermitSelf,
|
Write: api.PermitSelf,
|
||||||
BelongsTo: module,
|
// Do NOT register as belonging to the module, so that the API is available
|
||||||
|
// when something fails during starting of this module or a dependency.
|
||||||
ActionFunc: shutdown,
|
ActionFunc: shutdown,
|
||||||
Name: "Shut Down Portmaster",
|
Name: "Shut Down Portmaster",
|
||||||
Description: "Shut down the Portmaster Core Service and all UI components.",
|
Description: "Shut down the Portmaster Core Service and all UI components.",
|
||||||
@@ -36,9 +37,10 @@ func registerAPIEndpoints() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := api.RegisterEndpoint(api.Endpoint{
|
if err := api.RegisterEndpoint(api.Endpoint{
|
||||||
Path: "core/restart",
|
Path: "core/restart",
|
||||||
Write: api.PermitAdmin,
|
Write: api.PermitAdmin,
|
||||||
BelongsTo: module,
|
// Do NOT register as belonging to the module, so that the API is available
|
||||||
|
// when something fails during starting of this module or a dependency.
|
||||||
ActionFunc: restart,
|
ActionFunc: restart,
|
||||||
Name: "Restart Portmaster",
|
Name: "Restart Portmaster",
|
||||||
Description: "Restart the Portmaster Core Service.",
|
Description: "Restart the Portmaster Core Service.",
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func interceptionPrep() error {
|
|||||||
func resetAllConnectionVerdicts() {
|
func resetAllConnectionVerdicts() {
|
||||||
// Resetting will force all the connection to be evaluated by the firewall again
|
// Resetting will force all the connection to be evaluated by the firewall again
|
||||||
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
||||||
log.Info("interception: marking all connections for re-evaluation")
|
log.Info("interception: re-evaluating all connections")
|
||||||
|
|
||||||
// Create tracing context.
|
// Create tracing context.
|
||||||
ctx, tracer := log.AddTracer(context.Background())
|
ctx, tracer := log.AddTracer(context.Background())
|
||||||
@@ -137,7 +137,7 @@ func resetAllConnectionVerdicts() {
|
|||||||
previousVerdict := conn.Verdict.Firewall
|
previousVerdict := conn.Verdict.Firewall
|
||||||
|
|
||||||
// Apply privacy filter and check tunneling.
|
// Apply privacy filter and check tunneling.
|
||||||
filterConnection(ctx, conn, nil)
|
FilterConnection(ctx, conn, nil, true, true)
|
||||||
|
|
||||||
// Stop existing SPN tunnel if not needed anymore.
|
// Stop existing SPN tunnel if not needed anymore.
|
||||||
if conn.Verdict.Active != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
|
if conn.Verdict.Active != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
|
||||||
@@ -157,7 +157,7 @@ func resetAllConnectionVerdicts() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
tracer.Infof("profile: changed verdict on %d connections", changedVerdicts)
|
tracer.Infof("filter: changed verdict on %d connections", changedVerdicts)
|
||||||
tracer.Submit()
|
tracer.Submit()
|
||||||
|
|
||||||
err := interception.ResetVerdictOfAllConnections()
|
err := interception.ResetVerdictOfAllConnections()
|
||||||
@@ -437,14 +437,17 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||||
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
|
filterConnection := true
|
||||||
|
|
||||||
|
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
|
||||||
// Check for special (internal) connection cases.
|
// Check for special (internal) connection cases.
|
||||||
switch {
|
switch {
|
||||||
case !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort):
|
case !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort):
|
||||||
// Approve connection.
|
// Approve connection.
|
||||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||||
conn.Internal = true
|
conn.Internal = true
|
||||||
|
filterConnection = false
|
||||||
|
log.Tracer(pkt.Ctx()).Infof("filter: granting own pre-authenticated connection %s", conn)
|
||||||
|
|
||||||
// Redirect outbound DNS packets if enabled,
|
// Redirect outbound DNS packets if enabled,
|
||||||
case dnsQueryInterception() &&
|
case dnsQueryInterception() &&
|
||||||
@@ -461,9 +464,9 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
|||||||
conn.Entity.IPScope == netutils.LocalMulticast):
|
conn.Entity.IPScope == netutils.LocalMulticast):
|
||||||
|
|
||||||
// Reroute rogue dns queries back to Portmaster.
|
// Reroute rogue dns queries back to Portmaster.
|
||||||
conn.SetVerdictDirectly(network.VerdictRerouteToNameserver)
|
conn.SetVerdict(network.VerdictRerouteToNameserver, "redirecting rogue dns query", "", nil)
|
||||||
conn.Reason.Msg = "redirecting rogue dns query"
|
|
||||||
conn.Internal = true
|
conn.Internal = true
|
||||||
|
log.Tracer(pkt.Ctx()).Infof("filter: redirecting dns query %s to Portmaster", conn)
|
||||||
// End directly, as no other processing is necessary.
|
// End directly, as no other processing is necessary.
|
||||||
conn.StopFirewallHandler()
|
conn.StopFirewallHandler()
|
||||||
finalizeVerdict(conn)
|
finalizeVerdict(conn)
|
||||||
@@ -472,7 +475,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply privacy filter and check tunneling.
|
// Apply privacy filter and check tunneling.
|
||||||
filterConnection(pkt.Ctx(), conn, pkt)
|
FilterConnection(pkt.Ctx(), conn, pkt, filterConnection, true)
|
||||||
|
|
||||||
// Decide how to continue handling connection.
|
// Decide how to continue handling connection.
|
||||||
switch {
|
switch {
|
||||||
@@ -486,12 +489,15 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
// FilterConnection runs all the filtering (and tunneling) procedures.
|
||||||
if filterEnabled() {
|
func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet, checkFilter, checkTunnel bool) {
|
||||||
log.Tracer(ctx).Trace("filter: starting decision process")
|
if checkFilter {
|
||||||
DecideOnConnection(ctx, conn, pkt)
|
if filterEnabled() {
|
||||||
} else {
|
log.Tracer(ctx).Trace("filter: starting decision process")
|
||||||
conn.Accept("privacy filter disabled", noReasonOptionKey)
|
decideOnConnection(ctx, conn, pkt)
|
||||||
|
} else {
|
||||||
|
conn.Accept("privacy filter disabled", noReasonOptionKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Enable inspection framework again.
|
// TODO: Enable inspection framework again.
|
||||||
@@ -511,7 +517,9 @@ func filterConnection(ctx context.Context, conn *network.Connection, pkt packet.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if connection should be tunneled.
|
// Check if connection should be tunneled.
|
||||||
checkTunneling(ctx, conn)
|
if checkTunnel {
|
||||||
|
checkTunneling(ctx, conn)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle verdict records and transitions.
|
// Handle verdict records and transitions.
|
||||||
finalizeVerdict(conn)
|
finalizeVerdict(conn)
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ var defaultDeciders = []deciderFn{
|
|||||||
checkAutoPermitRelated,
|
checkAutoPermitRelated,
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecideOnConnection makes a decision about a connection.
|
// decideOnConnection makes a decision about a connection.
|
||||||
// When called, the connection and profile is already locked.
|
// When called, the connection and profile is already locked.
|
||||||
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
func decideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
||||||
// Check if we have a process and profile.
|
// Check if we have a process and profile.
|
||||||
layeredProfile := conn.Process().Profile()
|
layeredProfile := conn.Process().Profile()
|
||||||
if layeredProfile == nil {
|
if layeredProfile == nil {
|
||||||
@@ -141,15 +141,34 @@ func runDeciders(ctx context.Context, selectedDeciders []deciderFn, conn *networ
|
|||||||
// checkPortmasterConnection allows all connection that originate from
|
// checkPortmasterConnection allows all connection that originate from
|
||||||
// portmaster itself.
|
// portmaster itself.
|
||||||
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool {
|
func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool {
|
||||||
// Grant own outgoing connections.
|
// Grant own outgoing or local connections.
|
||||||
if conn.Process().Pid == ownPID && !conn.Inbound {
|
|
||||||
log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
|
// Blocking our own connections can lead to a very literal deadlock.
|
||||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
// This can currently happen, as fast-tracked connections are also
|
||||||
conn.Internal = true
|
// reset in the OS integration and might show up in the connection
|
||||||
return true
|
// handling if a packet in the other direction hits the firewall first.
|
||||||
|
|
||||||
|
// Ignore other processes.
|
||||||
|
if conn.Process().Pid != ownPID {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// Ignore inbound connection if non-local.
|
||||||
|
if conn.Inbound {
|
||||||
|
myIP, err := netenv.IsMyIP(conn.Entity.IP)
|
||||||
|
if err != nil {
|
||||||
|
log.Tracer(ctx).Debugf("filter: failed to check if %s is own IP for granting own connection: %s", conn.Entity.IP, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !myIP {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
|
||||||
|
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||||
|
conn.Internal = true
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkSelfCommunication checks if the process is communicating with itself.
|
// checkSelfCommunication checks if the process is communicating with itself.
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Check request with the privacy filter before resolving.
|
// Check request with the privacy filter before resolving.
|
||||||
firewall.DecideOnConnection(ctx, conn, nil)
|
firewall.FilterConnection(ctx, conn, nil, true, false)
|
||||||
|
|
||||||
// Check if there is a responder from the firewall.
|
// Check if there is a responder from the firewall.
|
||||||
// In special cases, the firewall might want to respond the query itself.
|
// In special cases, the firewall might want to respond the query itself.
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||||||
proc, inbound, err := process.GetProcessByConnection(pkt.Ctx(), pkt.Info())
|
proc, inbound, err := process.GetProcessByConnection(pkt.Ctx(), pkt.Info())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracer(pkt.Ctx()).Debugf("network: failed to find process of packet %s: %s", pkt, err)
|
log.Tracer(pkt.Ctx()).Debugf("network: failed to find process of packet %s: %s", pkt, err)
|
||||||
if inbound {
|
if inbound && !netutils.ClassifyIP(pkt.Info().Dst).IsLocalhost() {
|
||||||
proc = process.GetUnsolicitedProcess(pkt.Ctx())
|
proc = process.GetUnsolicitedProcess(pkt.Ctx())
|
||||||
} else {
|
} else {
|
||||||
proc = process.GetUnidentifiedProcess(pkt.Ctx())
|
proc = process.GetUnidentifiedProcess(pkt.Ctx())
|
||||||
|
|||||||
@@ -27,18 +27,10 @@ func (p *Process) GetProfile(ctx context.Context) (changed bool, err error) {
|
|||||||
// If not, continue with loading the profile.
|
// If not, continue with loading the profile.
|
||||||
log.Tracer(ctx).Trace("process: loading profile")
|
log.Tracer(ctx).Trace("process: loading profile")
|
||||||
|
|
||||||
// Check if there is a special profile for this process.
|
// Get special or regular profile.
|
||||||
localProfile, err := p.loadSpecialProfile(ctx)
|
localProfile, err := profile.GetLocalProfile(p.getSpecialProfileID(), p.MatchingData(), p.CreateProfileCallback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to load special profile: %w", err)
|
return false, fmt.Errorf("failed to find profile: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, find a regular profile for the process.
|
|
||||||
if localProfile == nil {
|
|
||||||
localProfile, err = profile.GetLocalProfile("", p.MatchingData(), p.CreateProfileCallback)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to find profile: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign profile to process.
|
// Assign profile to process.
|
||||||
@@ -48,10 +40,9 @@ func (p *Process) GetProfile(ctx context.Context) (changed bool, err error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadSpecialProfile attempts to load a special profile.
|
// getSpecialProfileID returns the special profile ID for the process, if any.
|
||||||
func (p *Process) loadSpecialProfile(_ context.Context) (*profile.Profile, error) {
|
func (p *Process) getSpecialProfileID() (specialProfileID string) {
|
||||||
// Check if we need a special profile.
|
// Check if we need a special profile.
|
||||||
var specialProfileID string
|
|
||||||
switch p.Pid {
|
switch p.Pid {
|
||||||
case UnidentifiedProcessID:
|
case UnidentifiedProcessID:
|
||||||
specialProfileID = profile.UnidentifiedProfileID
|
specialProfileID = profile.UnidentifiedProfileID
|
||||||
@@ -103,11 +94,5 @@ func (p *Process) loadSpecialProfile(_ context.Context) (*profile.Profile, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a special profile should be applied.
|
return specialProfileID
|
||||||
if specialProfileID == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return special profile.
|
|
||||||
return profile.GetSpecialProfile(specialProfileID, p.Path)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ func updateGlobalConfigProfile(ctx context.Context, task *modules.Task) error {
|
|||||||
|
|
||||||
action := cfgOptionDefaultAction()
|
action := cfgOptionDefaultAction()
|
||||||
switch action {
|
switch action {
|
||||||
case "permit":
|
case DefaultActionPermitValue:
|
||||||
cfgDefaultAction = DefaultActionPermit
|
cfgDefaultAction = DefaultActionPermit
|
||||||
case "ask":
|
case DefaultActionAskValue:
|
||||||
cfgDefaultAction = DefaultActionAsk
|
cfgDefaultAction = DefaultActionAsk
|
||||||
case "block":
|
case DefaultActionBlockValue:
|
||||||
cfgDefaultAction = DefaultActionBlock
|
cfgDefaultAction = DefaultActionBlock
|
||||||
default:
|
default:
|
||||||
// TODO: module error?
|
// TODO: module error?
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ var (
|
|||||||
cfgOptionDefaultAction config.StringOption
|
cfgOptionDefaultAction config.StringOption
|
||||||
cfgOptionDefaultActionOrder = 1
|
cfgOptionDefaultActionOrder = 1
|
||||||
|
|
||||||
|
DefaultActionPermitValue = "permit"
|
||||||
|
DefaultActionBlockValue = "block"
|
||||||
|
DefaultActionAskValue = "ask"
|
||||||
|
|
||||||
// Setting "Prompt Desktop Notifications" at order 2.
|
// Setting "Prompt Desktop Notifications" at order 2.
|
||||||
// Setting "Prompt Timeout" at order 3.
|
// Setting "Prompt Timeout" at order 3.
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ func registerConfiguration() error { //nolint:maintidx
|
|||||||
Key: CfgOptionDefaultActionKey,
|
Key: CfgOptionDefaultActionKey,
|
||||||
Description: `The default network action is applied when nothing else allows or blocks an outgoing connection. Incoming connections are always blocked by default.`,
|
Description: `The default network action is applied when nothing else allows or blocks an outgoing connection. Incoming connections are always blocked by default.`,
|
||||||
OptType: config.OptTypeString,
|
OptType: config.OptTypeString,
|
||||||
DefaultValue: "permit",
|
DefaultValue: DefaultActionPermitValue,
|
||||||
Annotations: config.Annotations{
|
Annotations: config.Annotations{
|
||||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||||
config.DisplayOrderAnnotation: cfgOptionDefaultActionOrder,
|
config.DisplayOrderAnnotation: cfgOptionDefaultActionOrder,
|
||||||
@@ -191,17 +195,17 @@ func registerConfiguration() error { //nolint:maintidx
|
|||||||
PossibleValues: []config.PossibleValue{
|
PossibleValues: []config.PossibleValue{
|
||||||
{
|
{
|
||||||
Name: "Allow",
|
Name: "Allow",
|
||||||
Value: "permit",
|
Value: DefaultActionPermitValue,
|
||||||
Description: "Allow all connections",
|
Description: "Allow all connections",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Block",
|
Name: "Block",
|
||||||
Value: "block",
|
Value: DefaultActionBlockValue,
|
||||||
Description: "Block all connections",
|
Description: "Block all connections",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "Prompt",
|
Name: "Prompt",
|
||||||
Value: "ask",
|
Value: DefaultActionAskValue,
|
||||||
Description: "Prompt for decisions",
|
Description: "Prompt for decisions",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -209,7 +213,7 @@ func registerConfiguration() error { //nolint:maintidx
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cfgOptionDefaultAction = config.Concurrent.GetAsString(CfgOptionDefaultActionKey, "permit")
|
cfgOptionDefaultAction = config.Concurrent.GetAsString(CfgOptionDefaultActionKey, DefaultActionPermitValue)
|
||||||
cfgStringOptions[CfgOptionDefaultActionKey] = cfgOptionDefaultAction
|
cfgStringOptions[CfgOptionDefaultActionKey] = cfgOptionDefaultAction
|
||||||
|
|
||||||
// Disable Auto Permit
|
// Disable Auto Permit
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/database"
|
||||||
"github.com/safing/portbase/database/query"
|
"github.com/safing/portbase/database/query"
|
||||||
"github.com/safing/portbase/database/record"
|
"github.com/safing/portbase/database/record"
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
@@ -50,6 +51,7 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
|||||||
|
|
||||||
// In some cases, we might need to get a profile directly, without matching data.
|
// In some cases, we might need to get a profile directly, without matching data.
|
||||||
// This could lead to inconsistent data - use with caution.
|
// This could lead to inconsistent data - use with caution.
|
||||||
|
// Example: Saving prompt results to profile should always be to the same ID!
|
||||||
if md == nil {
|
if md == nil {
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return nil, errors.New("cannot get local profiles without ID and matching data")
|
return nil, errors.New("cannot get local profiles without ID and matching data")
|
||||||
@@ -61,6 +63,26 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we are requesting a special profile.
|
||||||
|
var created, special bool
|
||||||
|
if id != "" && isSpecialProfileID(id) {
|
||||||
|
special = true
|
||||||
|
|
||||||
|
// Get special profile from DB.
|
||||||
|
if profile == nil {
|
||||||
|
profile, err = getProfile(makeScopedID(SourceLocal, id))
|
||||||
|
if err != nil && !errors.Is(err, database.ErrNotFound) {
|
||||||
|
log.Warningf("profile: failed to get special profile %s: %s", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create profile if not found or if it needs a reset.
|
||||||
|
if profile == nil || specialProfileNeedsReset(profile) {
|
||||||
|
profile = createSpecialProfile(id, md.Path())
|
||||||
|
created = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we don't have a profile yet, find profile based on matching data.
|
// If we don't have a profile yet, find profile based on matching data.
|
||||||
if profile == nil {
|
if profile == nil {
|
||||||
profile, err = findProfile(SourceLocal, md)
|
profile, err = findProfile(SourceLocal, md)
|
||||||
@@ -70,7 +92,6 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we still don't have a profile, create a new one.
|
// If we still don't have a profile, create a new one.
|
||||||
var created bool
|
|
||||||
if profile == nil {
|
if profile == nil {
|
||||||
created = true
|
created = true
|
||||||
|
|
||||||
@@ -105,7 +126,12 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
|||||||
// Initialize and update profile.
|
// Initialize and update profile.
|
||||||
|
|
||||||
// Update metadata.
|
// Update metadata.
|
||||||
changed := profile.updateMetadata(md.Path())
|
var changed bool
|
||||||
|
if special {
|
||||||
|
changed = updateSpecialProfileMetadata(profile, md.Path())
|
||||||
|
} else {
|
||||||
|
changed = profile.updateMetadata(md.Path())
|
||||||
|
}
|
||||||
|
|
||||||
// Save if created or changed.
|
// Save if created or changed.
|
||||||
if created || changed {
|
if created || changed {
|
||||||
@@ -117,7 +143,7 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trigger further metadata fetching from system if profile was created.
|
// Trigger further metadata fetching from system if profile was created.
|
||||||
if created && profile.UsePresentationPath {
|
if created && profile.UsePresentationPath && !special {
|
||||||
module.StartWorker("get profile metadata", profile.updateMetadataFromSystem)
|
module.StartWorker("get profile metadata", profile.updateMetadataFromSystem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package profile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
|
||||||
@@ -62,7 +61,8 @@ func migrateLinkedPath(ctx context.Context, _, to *version.Version, db *database
|
|||||||
// Get iterator over all profiles.
|
// Get iterator over all profiles.
|
||||||
it, err := db.Query(query.New(profilesDBPath))
|
it, err := db.Query(query.New(profilesDBPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to query profiles: %w", err)
|
log.Tracer(ctx).Errorf("profile: failed to migrate from linked path: failed to start query: %s", err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate all profiles.
|
// Migrate all profiles.
|
||||||
@@ -91,8 +91,8 @@ func migrateLinkedPath(ctx context.Context, _, to *version.Version, db *database
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there was an error while iterating.
|
// Check if there was an error while iterating.
|
||||||
if it.Err() != nil {
|
if err := it.Err(); err != nil {
|
||||||
return fmt.Errorf("profiles: failed to iterate over profiles for migration: %w", err)
|
log.Tracer(ctx).Errorf("profile: failed to migrate from linked path: failed to iterate over profiles for migration: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -175,11 +175,11 @@ func (profile *Profile) parseConfig() error {
|
|||||||
profile.defaultAction = DefaultActionNotSet
|
profile.defaultAction = DefaultActionNotSet
|
||||||
if ok {
|
if ok {
|
||||||
switch action {
|
switch action {
|
||||||
case "permit":
|
case DefaultActionPermitValue:
|
||||||
profile.defaultAction = DefaultActionPermit
|
profile.defaultAction = DefaultActionPermit
|
||||||
case "ask":
|
case DefaultActionAskValue:
|
||||||
profile.defaultAction = DefaultActionAsk
|
profile.defaultAction = DefaultActionAsk
|
||||||
case "block":
|
case DefaultActionBlockValue:
|
||||||
profile.defaultAction = DefaultActionBlock
|
profile.defaultAction = DefaultActionBlock
|
||||||
default:
|
default:
|
||||||
lastErr = fmt.Errorf(`default action "%s" invalid`, action)
|
lastErr = fmt.Errorf(`default action "%s" invalid`, action)
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package profile
|
package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portbase/database"
|
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portmaster/status"
|
"github.com/safing/portmaster/status"
|
||||||
)
|
)
|
||||||
@@ -13,7 +11,7 @@ const (
|
|||||||
// UnidentifiedProfileID is the profile ID used for unidentified processes.
|
// UnidentifiedProfileID is the profile ID used for unidentified processes.
|
||||||
UnidentifiedProfileID = "_unidentified"
|
UnidentifiedProfileID = "_unidentified"
|
||||||
// UnidentifiedProfileName is the name used for unidentified processes.
|
// UnidentifiedProfileName is the name used for unidentified processes.
|
||||||
UnidentifiedProfileName = "Unidentified App"
|
UnidentifiedProfileName = "Other Connections"
|
||||||
// UnidentifiedProfileDescription is the description used for unidentified processes.
|
// UnidentifiedProfileDescription is the description used for unidentified processes.
|
||||||
UnidentifiedProfileDescription = `Connections that could not be attributed to a specific app.
|
UnidentifiedProfileDescription = `Connections that could not be attributed to a specific app.
|
||||||
|
|
||||||
@@ -77,91 +75,19 @@ If you think you might have messed up the settings of the System DNS Client, jus
|
|||||||
PortmasterNotifierProfileDescription = `This is the Portmaster UI Tray Notifier.`
|
PortmasterNotifierProfileDescription = `This is the Portmaster UI Tray Notifier.`
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSpecialProfile fetches a special profile. This function ensures that the loaded profile
|
func isSpecialProfileID(id string) bool {
|
||||||
// is shared among all callers. Always provide all available data points.
|
switch id {
|
||||||
func GetSpecialProfile(id string, path string) ( //nolint:gocognit
|
case UnidentifiedProfileID,
|
||||||
profile *Profile,
|
UnsolicitedProfileID,
|
||||||
err error,
|
SystemProfileID,
|
||||||
) {
|
SystemResolverProfileID,
|
||||||
// Check if we have an ID.
|
PortmasterProfileID,
|
||||||
if id == "" {
|
PortmasterAppProfileID,
|
||||||
return nil, errors.New("cannot get special profile without ID")
|
PortmasterNotifierProfileID:
|
||||||
}
|
return true
|
||||||
scopedID := makeScopedID(SourceLocal, id)
|
|
||||||
|
|
||||||
// Globally lock getting a profile.
|
|
||||||
// This does not happen too often, and it ensures we really have integrity
|
|
||||||
// and no race conditions.
|
|
||||||
getProfileLock.Lock()
|
|
||||||
defer getProfileLock.Unlock()
|
|
||||||
|
|
||||||
// Check if there already is an active profile.
|
|
||||||
var previousVersion *Profile
|
|
||||||
profile = getActiveProfile(scopedID)
|
|
||||||
if profile != nil {
|
|
||||||
// Mark active and return if not outdated.
|
|
||||||
if profile.outdated.IsNotSet() {
|
|
||||||
profile.MarkStillActive()
|
|
||||||
return profile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If outdated, get from database.
|
|
||||||
previousVersion = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get special profile from DB and check if it needs a reset.
|
|
||||||
var created bool
|
|
||||||
profile, err = getProfile(scopedID)
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
// Reset profile if needed.
|
|
||||||
if specialProfileNeedsReset(profile) {
|
|
||||||
profile = createSpecialProfile(id, path)
|
|
||||||
created = true
|
|
||||||
}
|
|
||||||
case !errors.Is(err, database.ErrNotFound):
|
|
||||||
// Warn when fetching from DB fails, and create new profile as fallback.
|
|
||||||
log.Warningf("profile: failed to get special profile %s: %s", id, err)
|
|
||||||
fallthrough
|
|
||||||
default:
|
default:
|
||||||
// Create new profile if it does not exist (or failed to load).
|
return false
|
||||||
profile = createSpecialProfile(id, path)
|
|
||||||
created = true
|
|
||||||
}
|
}
|
||||||
// Check if creating the special profile was successful.
|
|
||||||
if profile == nil {
|
|
||||||
return nil, errors.New("given ID is not a special profile ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update metadata
|
|
||||||
changed := updateSpecialProfileMetadata(profile, path)
|
|
||||||
|
|
||||||
// Save if created or changed.
|
|
||||||
if created || changed {
|
|
||||||
err := profile.Save()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("profile: failed to save special profile %s: %s", scopedID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare profile for first use.
|
|
||||||
|
|
||||||
// If we are refetching, assign the layered profile from the previous version.
|
|
||||||
// The internal references will be updated when the layered profile checks for updates.
|
|
||||||
if previousVersion != nil && previousVersion.layeredProfile != nil {
|
|
||||||
profile.layeredProfile = previousVersion.layeredProfile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profiles must have a layered profile, create a new one if it
|
|
||||||
// does not yet exist.
|
|
||||||
if profile.layeredProfile == nil {
|
|
||||||
profile.layeredProfile = NewLayeredProfile(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the profile to the currently active profiles.
|
|
||||||
addActiveProfile(profile)
|
|
||||||
|
|
||||||
return profile, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (changed bool) {
|
func updateSpecialProfileMetadata(profile *Profile, binaryPath string) (changed bool) {
|
||||||
@@ -248,7 +174,7 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
|||||||
// Resolved domain from the system resolver are checked again when
|
// Resolved domain from the system resolver are checked again when
|
||||||
// attributed to a connection of a regular process. Otherwise, users
|
// attributed to a connection of a regular process. Otherwise, users
|
||||||
// would see two connection prompts for the same domain.
|
// would see two connection prompts for the same domain.
|
||||||
CfgOptionDefaultActionKey: "permit",
|
CfgOptionDefaultActionKey: DefaultActionPermitValue,
|
||||||
// Explicitly allow incoming connections.
|
// Explicitly allow incoming connections.
|
||||||
CfgOptionBlockInboundKey: status.SecurityLevelOff,
|
CfgOptionBlockInboundKey: status.SecurityLevelOff,
|
||||||
// Explicitly allow localhost and answers to multicast protocols that
|
// Explicitly allow localhost and answers to multicast protocols that
|
||||||
@@ -276,7 +202,29 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
|||||||
ID: PortmasterProfileID,
|
ID: PortmasterProfileID,
|
||||||
Source: SourceLocal,
|
Source: SourceLocal,
|
||||||
PresentationPath: path,
|
PresentationPath: path,
|
||||||
Internal: true,
|
Config: map[string]interface{}{
|
||||||
|
// In case anything slips through the internal self-allow, be sure to
|
||||||
|
// allow everything explicitly.
|
||||||
|
// Blocking connections here can lead to a very literal deadlock.
|
||||||
|
// This can currently happen, as fast-tracked connections are also
|
||||||
|
// reset in the OS integration and might show up in the connection
|
||||||
|
// handling if a packet in the other direction hits the firewall first.
|
||||||
|
CfgOptionDefaultActionKey: DefaultActionPermitValue,
|
||||||
|
CfgOptionBlockScopeInternetKey: status.SecurityLevelOff,
|
||||||
|
CfgOptionBlockScopeLANKey: status.SecurityLevelOff,
|
||||||
|
CfgOptionBlockScopeLocalKey: status.SecurityLevelOff,
|
||||||
|
CfgOptionBlockP2PKey: status.SecurityLevelOff,
|
||||||
|
CfgOptionBlockInboundKey: status.SecurityLevelOff,
|
||||||
|
CfgOptionEndpointsKey: []string{
|
||||||
|
"+ *",
|
||||||
|
},
|
||||||
|
CfgOptionServiceEndpointsKey: []string{
|
||||||
|
"+ Localhost",
|
||||||
|
"+ LAN",
|
||||||
|
"- *",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Internal: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
case PortmasterAppProfileID:
|
case PortmasterAppProfileID:
|
||||||
@@ -285,7 +233,7 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
|||||||
Source: SourceLocal,
|
Source: SourceLocal,
|
||||||
PresentationPath: path,
|
PresentationPath: path,
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
CfgOptionDefaultActionKey: "block",
|
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||||
CfgOptionEndpointsKey: []string{
|
CfgOptionEndpointsKey: []string{
|
||||||
"+ Localhost",
|
"+ Localhost",
|
||||||
"+ .safing.io",
|
"+ .safing.io",
|
||||||
@@ -300,7 +248,7 @@ func createSpecialProfile(profileID string, path string) *Profile {
|
|||||||
Source: SourceLocal,
|
Source: SourceLocal,
|
||||||
PresentationPath: path,
|
PresentationPath: path,
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
CfgOptionDefaultActionKey: "block",
|
CfgOptionDefaultActionKey: DefaultActionBlockValue,
|
||||||
CfgOptionEndpointsKey: []string{
|
CfgOptionEndpointsKey: []string{
|
||||||
"+ Localhost",
|
"+ Localhost",
|
||||||
},
|
},
|
||||||
@@ -334,6 +282,8 @@ func specialProfileNeedsReset(profile *Profile) bool {
|
|||||||
switch profile.ID {
|
switch profile.ID {
|
||||||
case SystemResolverProfileID:
|
case SystemResolverProfileID:
|
||||||
return canBeUpgraded(profile, "21.10.2022")
|
return canBeUpgraded(profile, "21.10.2022")
|
||||||
|
case PortmasterProfileID:
|
||||||
|
return canBeUpgraded(profile, "21.10.2022")
|
||||||
case PortmasterAppProfileID:
|
case PortmasterAppProfileID:
|
||||||
return canBeUpgraded(profile, "8.9.2021")
|
return canBeUpgraded(profile, "8.9.2021")
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user