Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
*.pyc
*.profile

ui/opensnitch/proto/
daemon/ui/protocol/

.vscode/
.idea/
.DS_Store
28 changes: 21 additions & 7 deletions daemon/rule/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/evilsocket/opensnitch/daemon/conman"
Expand All @@ -26,15 +27,20 @@ type Loader struct {
watcher *fsnotify.Watcher
rules map[string]*Rule
activeRules []string
activeSnapshot atomic.Pointer[activeRulesSnapshot]
Path string
liveReload bool
liveReloadRunning bool
checkSums bool
checkSums atomic.Bool
stopLiveReload chan struct{}

sync.RWMutex
}

type activeRulesSnapshot struct {
rules []*Rule
}

// NewLoader loads rules from disk, and watches for changes made to the rules files
// on disk.
func NewLoader(liveReload bool) (*Loader, error) {
Expand Down Expand Up @@ -69,7 +75,7 @@ func (l *Loader) GetAll() map[string]*Rule {
// EnableChecksums enables checksums field for rules globally.
func (l *Loader) EnableChecksums(enable bool) {
log.Debug("[rules loader] EnableChecksums: %v", enable)
l.checkSums = enable
l.checkSums.Store(enable)
procmon.EventsCache.SetComputeChecksums(enable)
procmon.EventsCache.AddChecksumHash(string(OpProcessHashMD5))
}
Expand Down Expand Up @@ -113,6 +119,7 @@ func (l *Loader) Reload(path string) error {
l.Lock()
l.activeRules = make([]string, 0)
l.rules = make(map[string]*Rule)
l.activeSnapshot.Store(nil)
l.Unlock()
return l.Load(path)
}
Expand Down Expand Up @@ -367,6 +374,7 @@ func (l *Loader) unmarshalOperatorList(op *Operator) error {

func (l *Loader) sortRules() {
l.activeRules = make([]string, 0, len(l.rules))
orderedRules := make([]*Rule, 0, len(l.rules))
for k, r := range l.rules {
// exclude not enabled rules from the list of active rules
if !r.Enabled {
Expand All @@ -375,6 +383,10 @@ func (l *Loader) sortRules() {
l.activeRules = append(l.activeRules, k)
}
sort.Strings(l.activeRules)
for _, name := range l.activeRules {
orderedRules = append(orderedRules, l.rules[name])
}
l.activeSnapshot.Store(&activeRulesSnapshot{rules: orderedRules})
}

func (l *Loader) addUserRule(rule *Rule) {
Expand Down Expand Up @@ -495,12 +507,14 @@ Exit:

// FindFirstMatch will try match the connection against the existing rule set.
func (l *Loader) FindFirstMatch(con *conman.Connection) (match *Rule) {
l.RLock()
defer l.RUnlock()
snapshot := l.activeSnapshot.Load()
if snapshot == nil {
return nil
}
hasChecksums := l.checkSums.Load()

for _, idx := range l.activeRules {
rule, _ := l.rules[idx]
if rule.Match(con, l.checkSums) {
for _, rule := range snapshot.rules {
if rule.Match(con, hasChecksums) {
// We have a match.
// Save the rule in order to don't ask the user to take action,
// and keep iterating until a Deny or a Priority rule appears.
Expand Down
121 changes: 101 additions & 20 deletions daemon/rule/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"

"github.com/evilsocket/opensnitch/daemon/conman"
"github.com/evilsocket/opensnitch/daemon/core"
Expand Down Expand Up @@ -75,13 +76,32 @@ const (
type opCallback func(value string) bool
type opGenericCallback func(value interface{}) bool

type listRegexEntry struct {
file string
re *regexp.Regexp
}

type listCacheSnapshot struct {
lists map[string]interface{}
domainWildcards domainWildcardTrie
domainGlobs []string
listExact map[string]struct{}
listNets []*net.IPNet
regexEntries []listRegexEntry
}

// Operator represents what we want to filter of a connection, and how.
type Operator struct {
cb opCallback
cbGeneric opGenericCallback
re *regexp.Regexp
netMask *net.IPNet
lists map[string]interface{}
domainWildcards domainWildcardTrie
domainGlobs []string
listExact map[string]struct{}
listNets []*net.IPNet
listSnapshot atomic.Pointer[listCacheSnapshot]
exitMonitorChan chan (struct{})
rangeMin uint64
rangeMax uint64
Expand Down Expand Up @@ -178,10 +198,10 @@ func (o *Operator) Compile() error {
o.cb = o.reListCmp
} else if o.Operand == OpIPLists {
o.loadLists()
o.cb = o.simpleListsCmp
o.cbGeneric = o.ipListsCmp
} else if o.Operand == OpNetLists {
o.loadLists()
o.cbGeneric = o.ipNetCmp
o.cbGeneric = o.netListsCmp
} else if o.Operand == OpHashMD5Lists {
o.loadLists()
o.cb = o.simpleListsCmp
Expand Down Expand Up @@ -290,9 +310,12 @@ func (o *Operator) cmpNetwork(destIP interface{}) bool {
}

func (o *Operator) matchListsCmp(msg, what string) bool {
o.RLock()
item, found := o.lists[what]
o.RUnlock()
snapshot := o.listSnapshot.Load()
if snapshot == nil {
return false
}

item, found := snapshot.lists[what]

if found {
log.Debug("%s: %s, %s", log.Red(msg), what, item)
Expand All @@ -309,7 +332,29 @@ func (o *Operator) domainsListsCmp(data string) bool {
data = strings.ToLower(data)
}

return o.matchListsCmp("domains list match", data)
snapshot := o.listSnapshot.Load()
if snapshot == nil {
return false
}

_, exactFound := snapshot.lists[data]

if exactFound {
log.Debug("%s: %s", log.Red("domains list match"), data)
return true
}
if snapshot.domainWildcards.matchesHost(data) {
log.Debug("%s: %s", log.Red("domains wildcard match"), data)
return true
}
for _, g := range snapshot.domainGlobs {
if matchDomainGlob(g, data) {
log.Debug("%s: %s", log.Red("domains glob match"), data)
return true
}
}

return false
}

func (o *Operator) simpleListsCmp(what string) bool {
Expand All @@ -320,17 +365,52 @@ func (o *Operator) simpleListsCmp(what string) bool {
return o.matchListsCmp("simple list match", what)
}

func (o *Operator) ipNetCmp(dstIP interface{}) bool {
o.RLock()
defer o.RUnlock()
func (o *Operator) netListsCmp(dstIP interface{}) bool {
ip := dstIP.(net.IP)
ipText := ip.String()
snapshot := o.listSnapshot.Load()
if snapshot == nil {
return false
}

_, exactFound := snapshot.listExact[ipText]

if exactFound {
log.Debug("%s: %s", log.Red("Net exact list match"), ipText)
return true
}

for _, netMask := range snapshot.listNets {
if netMask.Contains(ip) {
log.Debug("%s: %s, %s", log.Red("Net list match"), ipText, netMask.String())
return true
}
}
return false
}

func (o *Operator) ipListsCmp(dstIP interface{}) bool {
ip := dstIP.(net.IP)
ipText := ip.String()
snapshot := o.listSnapshot.Load()
if snapshot == nil {
return false
}

_, exactFound := snapshot.listExact[ipText]

if exactFound {
log.Debug("%s: %s", log.Red("IP list exact match"), ipText)
return true
}

for host, netMask := range o.lists {
n := netMask.(*net.IPNet)
if n.Contains(dstIP.(net.IP)) {
log.Debug("%s: %s, %s", log.Red("Net list match"), dstIP, host)
for _, netMask := range snapshot.listNets {
if netMask.Contains(ip) {
log.Debug("%s: %s, %s", log.Red("IP list cidr match"), ipText, netMask.String())
return true
}
}

return false
}

Expand All @@ -341,13 +421,14 @@ func (o *Operator) reListCmp(data string) bool {
if o.Sensitive == false {
data = strings.ToLower(data)
}
o.RLock()
defer o.RUnlock()
snapshot := o.listSnapshot.Load()
if snapshot == nil {
return false
}

for file, re := range o.lists {
r := re.(*regexp.Regexp)
if r.MatchString(data) {
log.Debug("%s: %s, %s", log.Red("Regexp list match"), data, file)
for _, entry := range snapshot.regexEntries {
if entry.re.MatchString(data) {
log.Debug("%s: %s, %s", log.Red("Regexp list match"), data, entry.file)
return true
}
}
Expand Down Expand Up @@ -389,7 +470,7 @@ func (o *Operator) Match(con *conman.Connection, hasChecksums bool) bool {
} else if o.Operand == OpDomainsLists {
return o.cb(con.DstHost)
} else if o.Operand == OpIPLists {
return o.cb(con.DstIP.String())
return o.cbGeneric(con.DstIP)
} else if o.Operand == OpHashMD5Lists {
return o.cb(con.Process.Checksums[procmon.HashMD5])
} else if o.Operand == OpUserID || o.Operand == OpUserName {
Expand Down
Loading
Loading