Work on portmaster restructuring

This commit is contained in:
Daniel 2018-11-30 22:11:00 +01:00
parent 3990790f17
commit 62b1c03edc
13 changed files with 349 additions and 112 deletions

View file

@ -34,40 +34,40 @@ func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string)
// grant self
if connection.Process().Pid == os.Getpid() {
log.Infof("sheriff: granting own connection %s", connection)
log.Infof("firewall: granting own connection %s", connection)
connection.Accept()
return
}
// check if there is a profile
profile := connection.Process().Profile
profileSet := connection.Process().ProfileSetSet
if profile == nil {
log.Infof("sheriff: no profile, denying connection %s", connection)
log.Infof("firewall: no profile, denying connection %s", connection)
connection.AddReason("no profile")
connection.Block()
return
}
// check user class
if profile.Flags.Has(profile.System) {
if profileSet.CheckFlag(profile.System) {
if !connection.Process().IsSystem() {
log.Infof("sheriff: denying connection %s, profile has System flag set, but process is not executed by System", connection)
log.Infof("firewall: denying connection %s, profile has System flag set, but process is not executed by System", connection)
connection.AddReason("must be executed by system")
connection.Block()
return
}
}
if profile.Flags.Has(profile.Admin) {
if profileSet.CheckFlag(profile.Admin) {
if !connection.Process().IsAdmin() {
log.Infof("sheriff: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
log.Infof("firewall: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
connection.AddReason("must be executed by admin")
connection.Block()
return
}
}
if profile.Flags.Has(profile.User) {
if profileSet.CheckFlag(profile.User) {
if !connection.Process().IsUser() {
log.Infof("sheriff: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
log.Infof("firewall: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
connection.AddReason("must be executed by user")
connection.Block()
return
@ -75,8 +75,8 @@ func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string)
}
// check for any network access
if !profile.Flags.Has(profile.Internet) && !profile.Flags.Has(profile.LocalNet) {
log.Infof("sheriff: denying connection %s, profile denies Internet and local network access", connection)
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LocalNet) {
log.Infof("firewall: denying connection %s, profile denies Internet and local network access", connection)
connection.Block()
return
}
@ -102,14 +102,14 @@ func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string)
}
if matched {
if profile.DomainWhitelistIsBlacklist {
log.Infof("sheriff: denying connection %s, profile has %s in domain blacklist", connection, fqdn)
log.Infof("firewall: denying connection %s, profile has %s in domain blacklist", connection, fqdn)
connection.AddReason("domain blacklisted")
connection.Block()
return
}
} else {
if !profile.DomainWhitelistIsBlacklist {
log.Infof("sheriff: denying connection %s, profile does not have %s in domain whitelist", connection, fqdn)
log.Infof("firewall: denying connection %s, profile does not have %s in domain whitelist", connection, fqdn)
connection.AddReason("domain not in whitelist")
connection.Block()
return
@ -127,9 +127,10 @@ func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, r
// - network specific: Strict
// check if there is a profile
profile := connection.Process().Profile
if profile == nil {
log.Infof("sheriff: no profile, denying connection %s", connection)
profileSet := connection.Process().ProfileSet
// FIXME: there should always be a profile
if profileSet == nil {
log.Infof("firewall: no profile, denying connection %s", connection)
connection.AddReason("no profile")
connection.Block()
return rrCache
@ -137,7 +138,7 @@ func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, r
// check Strict flag
// TODO: drastically improve this!
if profile.Flags.Has(profile.Strict) {
if profileSet.CheckFlag(profile.Related) {
matched := false
pathElements := strings.Split(connection.Process().Path, "/")
if len(pathElements) > 2 {
@ -162,7 +163,7 @@ func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, r
}
}
if !matched {
log.Infof("sheriff: denying connection %s, profile has declared Strict flag and no match to domain was found", connection)
log.Infof("firewall: denying connection %s, profile has declared Strict flag and no match to domain was found", connection)
connection.AddReason("domain does not relate to process")
connection.Block()
return rrCache
@ -195,40 +196,40 @@ func DecideOnConnection(connection *network.Connection, pkt packet.Packet) {
// grant self
if connection.Process().Pid == os.Getpid() {
log.Infof("sheriff: granting own connection %s", connection)
log.Infof("firewall: granting own connection %s", connection)
connection.Accept()
return
}
// check if there is a profile
profile := connection.Process().Profile
profileSet := connection.Process().ProfileSet
if profile == nil {
log.Infof("sheriff: no profile, denying connection %s", connection)
log.Infof("firewall: no profile, denying connection %s", connection)
connection.AddReason("no profile")
connection.Block()
return
}
// check user class
if profile.Flags.Has(profile.System) {
if profileSet.CheckFlag(profile.System) {
if !connection.Process().IsSystem() {
log.Infof("sheriff: denying connection %s, profile has System flag set, but process is not executed by System", connection)
log.Infof("firewall: denying connection %s, profile has System flag set, but process is not executed by System", connection)
connection.AddReason("must be executed by system")
connection.Block()
return
}
}
if profile.Flags.Has(profile.Admin) {
if profileSet.CheckFlag(profile.Admin) {
if !connection.Process().IsAdmin() {
log.Infof("sheriff: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
log.Infof("firewall: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
connection.AddReason("must be executed by admin")
connection.Block()
return
}
}
if profile.Flags.Has(profile.User) {
if profileSet.CheckFlag(profile.User) {
if !connection.Process().IsUser() {
log.Infof("sheriff: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
log.Infof("firewall: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
connection.AddReason("must be executed by user")
connection.Block()
return
@ -236,8 +237,8 @@ func DecideOnConnection(connection *network.Connection, pkt packet.Packet) {
}
// check for any network access
if !profile.Flags.Has(profile.Internet) && !profile.Flags.Has(profile.LocalNet) {
log.Infof("sheriff: denying connection %s, profile denies Internet and local network access", connection)
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LocalNet) {
log.Infof("firewall: denying connection %s, profile denies Internet and local network access", connection)
connection.AddReason("no network access allowed")
connection.Block()
return
@ -246,29 +247,29 @@ func DecideOnConnection(connection *network.Connection, pkt packet.Packet) {
switch connection.Domain {
case "I":
// check Service flag
if !profile.Flags.Has(profile.Service) {
log.Infof("sheriff: denying connection %s, profile does not declare service", connection)
if !profileSet.CheckFlag(profile.Service) {
log.Infof("firewall: denying connection %s, profile does not declare service", connection)
connection.AddReason("not a service")
connection.Drop()
return
}
// check if incoming connections are allowed on any port, but only if there no other restrictions
if !!profile.Flags.Has(profile.Internet) && !!profile.Flags.Has(profile.LocalNet) && len(profile.ListenPorts) == 0 {
log.Infof("sheriff: granting connection %s, profile allows incoming connections from anywhere and on any port", connection)
if !!profileSet.CheckFlag(profile.Internet) && !!profileSet.CheckFlag(profile.LocalNet) && len(profile.ListenPorts) == 0 {
log.Infof("firewall: granting connection %s, profile allows incoming connections from anywhere and on any port", connection)
connection.Accept()
return
}
case "D":
// check Directconnect flag
if !profile.Flags.Has(profile.Directconnect) {
log.Infof("sheriff: denying connection %s, profile does not declare direct connections", connection)
// check PeerToPeer flag
if !profileSet.CheckFlag(profile.PeerToPeer) {
log.Infof("firewall: denying connection %s, profile does not declare direct connections", connection)
connection.AddReason("direct connections (without DNS) not allowed")
connection.Drop()
return
}
}
log.Infof("sheriff: could not decide on connection %s, deciding on per-link basis", connection)
log.Infof("firewall: could not decide on connection %s, deciding on per-link basis", connection)
connection.CantSay()
}
@ -280,9 +281,9 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
// Profile.ListenPorts
// check if there is a profile
profile := connection.Process().Profile
profileSet := connection.Process().ProfileSet
if profile == nil {
log.Infof("sheriff: no profile, denying %s", link)
log.Infof("firewall: no profile, denying %s", link)
link.AddReason("no profile")
link.UpdateVerdict(network.BLOCK)
return
@ -296,15 +297,15 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
remoteIP = pkt.GetIPHeader().Dst
}
if netutils.IPIsLocal(remoteIP) {
if !profile.Flags.Has(profile.LocalNet) {
log.Infof("sheriff: dropping link %s, profile does not allow communication in the local network", link)
if !profileSet.CheckFlag(profile.LocalNet) {
log.Infof("firewall: dropping link %s, profile does not allow communication in the local network", link)
link.AddReason("profile does not allow access to local network")
link.UpdateVerdict(network.BLOCK)
return
}
} else {
if !profile.Flags.Has(profile.Internet) {
log.Infof("sheriff: dropping link %s, profile does not allow communication with the Internet", link)
if !profileSet.CheckFlag(profile.Internet) {
log.Infof("firewall: dropping link %s, profile does not allow communication with the Internet", link)
link.AddReason("profile does not allow access to the Internet")
link.UpdateVerdict(network.BLOCK)
return
@ -316,7 +317,7 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
tcpUdpHeader := pkt.GetTCPUDPHeader()
if tcpUdpHeader == nil {
log.Infof("sheriff: blocking link %s, profile has declared connect port whitelist, but link is not TCP/UDP", link)
log.Infof("firewall: blocking link %s, profile has declared connect port whitelist, but link is not TCP/UDP", link)
link.AddReason("profile has declared connect port whitelist, but link is not TCP/UDP")
link.UpdateVerdict(network.BLOCK)
return
@ -339,7 +340,7 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
}
if !matched {
log.Infof("sheriff: blocking link %s, remote port %d not in profile connect port whitelist", link, remotePort)
log.Infof("firewall: blocking link %s, remote port %d not in profile connect port whitelist", link, remotePort)
link.AddReason("destination port not in whitelist")
link.UpdateVerdict(network.BLOCK)
return
@ -352,7 +353,7 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
tcpUdpHeader := pkt.GetTCPUDPHeader()
if tcpUdpHeader == nil {
log.Infof("sheriff: dropping link %s, profile has declared listen port whitelist, but link is not TCP/UDP", link)
log.Infof("firewall: dropping link %s, profile has declared listen port whitelist, but link is not TCP/UDP", link)
link.AddReason("profile has declared listen port whitelist, but link is not TCP/UDP")
link.UpdateVerdict(network.DROP)
return
@ -375,7 +376,7 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
}
if !matched {
log.Infof("sheriff: blocking link %s, local port %d not in profile listen port whitelist", link, localPort)
log.Infof("firewall: blocking link %s, local port %d not in profile listen port whitelist", link, localPort)
link.AddReason("listen port not in whitelist")
link.UpdateVerdict(network.BLOCK)
return
@ -383,7 +384,7 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
}
log.Infof("sheriff: accepting link %s", link)
log.Infof("firewall: accepting link %s", link)
link.UpdateVerdict(network.ACCEPT)
}

View file

@ -0,0 +1,43 @@
package reference
import "strconv"
var (
protocolNames = map[uint8]string{
1: "ICMP",
2: "IGMP",
6: "TCP",
17: "UDP",
27: "RDP",
33: "DCCP",
136: "UDPLite",
}
protocolNumbers = map[string]uint8{
"ICMP": 1,
"IGMP": 2,
"TCP": 6,
"UDP": 17,
"RDP": 27,
"DCCP": 33,
"UDPLite": 136,
}
)
// GetProtocolName returns the name of a IP protocol number.
func GetProtocolName(protocol uint8) (name string) {
name, ok := protocolNames[protocol]
if ok {
return name
}
return strconv.Itoa(int(protocol))
}
// GetProtocolNumber returns the number of a IP protocol name.
func GetProtocolNumber(protocol string) (number uint8, ok bool) {
number, ok = protocolNumbers[protocol]
if ok {
return number, true
}
return 0, false
}

43
process/executable.go Normal file
View file

@ -0,0 +1,43 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"crypto"
"encoding/hex"
"hash"
"io"
"os"
)
// GetExecHash returns the hash of the executable with the given algorithm.
func (p *Process) GetExecHash(algorithm string) (string, error) {
sum, ok := p.ExecHashes[algorithm]
if ok {
return sum, nil
}
var hasher hash.Hash
switch algorithm {
case "md5":
hasher = crypto.MD5.New()
case "sha1":
hasher = crypto.SHA1.New()
case "sha256":
hasher = crypto.SHA256.New()
}
file, err := os.Open(p.Path)
if err != nil {
return "", err
}
_, err = io.Copy(hasher, file)
if err != nil {
return "", err
}
sum = hex.EncodeToString(hasher.Sum(nil))
p.ExecHashes[algorithm] = sum
return sum, nil
}

View file

@ -1,37 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package process
import (
"strings"
"sync"
"time"
"github.com/Safing/portbase/database/record"
)
// ExecutableSignature stores a signature of an executable.
type ExecutableSignature []byte
// FileInfo stores (security) information about a file.
type FileInfo struct {
record.Base
sync.Mutex
HumanName string
Owners []string
ApproxLastSeen int64
Signature *ExecutableSignature
}
// GetFileInfo gathers information about a file and returns *FileInfo
func GetFileInfo(path string) *FileInfo {
// TODO: actually get file information
// TODO: try to load from DB
// TODO: save to DB (key: hash of some sorts)
splittedPath := strings.Split("/", path)
return &FileInfo{
HumanName: splittedPath[len(splittedPath)-1],
ApproxLastSeen: time.Now().Unix(),
}
}

51
process/matching.go Normal file
View file

@ -0,0 +1,51 @@
package process
import (
"github.com/Safing/portbase/log"
"github.com/Safing/portmaster/profile"
)
// FindProfiles finds and assigns a profile set to the process.
func (p *Process) FindProfiles() {
// Get fingerprints of process
// Check if user profile already exists, else create new
// Find/Re-evaluate Stamp profile
// p.UserProfileKey
// p.profileSet
}
func matchProfile(p *Process, prof *profile.Profile) (score int) {
for _, fp := range prof.Fingerprints {
score += matchFingerprint(p, fp)
}
return
}
func matchFingerprint(p *Process, fp *profile.Fingerprint) (score int) {
if !fp.MatchesOS() {
return 0
}
switch fp.Type {
case "full_path":
if p.Path == fp.Value {
}
return profile.GetFingerprintWeight(fp.Type)
case "partial_path":
return profile.GetFingerprintWeight(fp.Type)
case "md5_sum", "sha1_sum", "sha256_sum":
sum, err := p.GetExecHash(fp.Type)
if err != nil {
log.Errorf("process: failed to get hash of executable: %s", err)
} else if sum == fp.Value {
return profile.GetFingerprintWeight(fp.Type)
}
}
return 0
}

View file

@ -5,6 +5,7 @@ package process
import (
"fmt"
"runtime"
"strings"
"sync"
"time"
@ -27,13 +28,18 @@ type Process struct {
ParentPid int
Path string
Cwd string
FileInfo *FileInfo
CmdLine string
FirstArg string
profileSet *profile.Set
Name string
Icon string
ExecName string
ExecHashes map[string]string
// ExecOwner ...
// ExecSignature ...
UserProfileKey string
profileSet *profile.Set
Name string
Icon string
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache.
FirstConnectionEstablished int64
@ -226,8 +232,11 @@ func GetOrFindProcess(pid int) (*Process, error) {
// }
// }
// get FileInfo
new.FileInfo = GetFileInfo(new.Path)
// Executable Information
// FIXME: use os specific path seperator
splittedPath := strings.Split("/", new.Path)
new.ExecName = strings.ToTitle(splittedPath[len(splittedPath)-1])
}

View file

@ -1,6 +1,6 @@
package profile
// OS Identifier Prefix
// OS Identifier
const (
IdentifierPrefix = "mac:"
osIdentifier = "mac"
)

View file

@ -1,6 +1,6 @@
package profile
// OS Identifier Prefix
// OS Identifier
const (
IdentifierPrefix = "lin:"
osIdentifier = "lin"
)

99
profile/fingerprint.go Normal file
View file

@ -0,0 +1,99 @@
package profile
var (
fingerprintWeights = map[string]int{
"full_path": 2,
"partial_path": 1,
"md5_sum": 4,
"sha1_sum": 5,
"sha256_sum": 6,
}
)
type Fingerprint struct {
OS string
Type string
Value string
Comment string
}
func (fp *Fingerprint) MatchesOS() bool {
return fp.OS == osIdentifier
}
//
// func (fp *Fingerprint) Equals(other *Fingerprint) bool {
// return fp.OS == other.OS &&
// fp.Type == other.Type &&
// fp.Value == other.Value
// }
//
// func (fp *Fingerprint) Check(type, value string) (weight int) {
// if fp.Match(fpType, value) {
// return GetFingerprintWeight(fpType)
// }
// return 0
// }
//
// func (fp *Fingerprint) Match(fpType, value string) (matches bool) {
// switch fp.Type {
// case "partial_path":
// return
// default:
// return fp.OS == osIdentifier &&
// fp.Type == fpType &&
// fp.Value == value
// }
//
func GetFingerprintWeight(fpType string) (weight int) {
weight, ok := fingerprintWeights[fpType]
if ok {
return weight
}
return 0
}
//
// func (p *Profile) GetApplicableFingerprints() (fingerprints []*Fingerprint) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier {
// fingerprints = append(fingerprints, fp)
// }
// }
// return
// }
//
// func (p *Profile) AddFingerprint(fp *Fingerprint) error {
// if fp.OS == "" {
// fp.OS = osIdentifier
// }
//
// p.Fingerprints = append(p.Fingerprints, fp)
// return p.Save()
// }
//
// func (p *Profile) GetApplicableFingerprintTypes() (types []string) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier && !utils.StringInSlice(types, fp.Type) {
// types = append(types, fp.Type)
// }
// }
// return
// }
//
// func (p *Profile) MatchFingerprints(fingerprints map[string]string) (score int) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier {
//
// }
// }
// return
// }
//
// func FindUserProfiles() {
//
// }
//
// func FindProfiles(path string) (*ProfileSet, error) {
//
// }

View file

@ -4,21 +4,20 @@ import (
"fmt"
"strconv"
"strings"
"github.com/Safing/portmaster/network/reference"
)
// Ports is a list of permitted or denied ports
type Ports map[string][]*Port
type Ports map[int16][]*Port
// Check returns whether listening/connecting to a certain port is allowed, if set.
func (p Ports) Check(listen bool, protocol string, port uint16) (permit, ok bool) {
func (p Ports) Check(signedProtocol int16, port uint16) (permit, ok bool) {
if p == nil {
return false, false
}
if listen {
protocol = "<" + protocol
}
portDefinitions, ok := p[protocol]
portDefinitions, ok := p[signedProtocol]
if ok {
for _, portD := range portDefinitions {
if portD.Matches(port) {
@ -29,16 +28,23 @@ func (p Ports) Check(listen bool, protocol string, port uint16) (permit, ok bool
return false, false
}
func formatSignedProtocol(sP int16) string {
if sP < 0 {
return fmt.Sprintf("<%s", reference.GetProtocolName(uint8(-1*sP)))
}
return reference.GetProtocolName(uint8(sP))
}
func (p Ports) String() string {
var s []string
for protocol, ports := range p {
for signedProtocol, ports := range p {
var portStrings []string
for _, port := range ports {
portStrings = append(portStrings, port.String())
}
s = append(s, fmt.Sprintf("%s:[%s]", protocol, strings.Join(portStrings, ", ")))
s = append(s, fmt.Sprintf("%s:[%s]", formatSignedProtocol(signedProtocol), strings.Join(portStrings, ", ")))
}
if len(s) == 0 {

View file

@ -23,8 +23,7 @@ type Profile struct {
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for a database path or "e:" for the encoded data.
Icon string
// Identification
Identifiers []string
// Fingerprints
Fingerprints []string
// The mininum security level to apply to connections made with this profile
@ -33,7 +32,8 @@ type Profile struct {
Domains Domains
Ports Ports
StampProfileKey string
StampProfileKey string
StampProfileAssigned int64
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
// Framework *Framework `json:",omitempty bson:",omitempty"`

View file

@ -51,7 +51,7 @@ func (set *Set) Update(securityLevel uint8) {
}
// update independence
if active, ok := set.CheckFlag(Independent); active && ok {
if set.CheckFlag(Independent) {
set.independent = true
} else {
set.independent = false
@ -59,7 +59,7 @@ func (set *Set) Update(securityLevel uint8) {
}
// CheckFlag returns whether a given flag is set.
func (set *Set) CheckFlag(flag) (active bool) {
func (set *Set) CheckFlag(flag uint8) (active bool) {
for i, profile := range set.profiles {
if i == 2 && set.independent {
@ -97,7 +97,12 @@ func (set *Set) CheckDomain(domain string) (permit, ok bool) {
}
// Ports returns the highest prioritized Ports configuration.
func (set *Set) CheckPort() (permit, ok bool) {
func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) {
signedProtocol := int16(protocol)
if listen {
signedProtocol = -1 * signedProtocol
}
for i, profile := range set.profiles {
if i == 2 && set.independent {
@ -105,13 +110,13 @@ func (set *Set) CheckPort() (permit, ok bool) {
}
if profile != nil {
if profile.Ports.Check() {
return profile.Ports
if permit, ok = profile.Ports.Check(signedProtocol, port); ok {
return
}
}
}
return false, false
return false, false
}
// SecurityLevel returns the highest prioritized security level.

View file

@ -0,0 +1,17 @@
package profile
import "testing"
func TestProfileSet(t *testing.T) {
// new := &Set{
// profiles: [4]*Profile{
// user, // Application
// nil, // Global
// stamp, // Stamp
// nil, // Default
// },
// }
// new.Update(status.SecurityLevelFortress)
}