From f47e23e706bfe6c88e14227938ef03224f70e640 Mon Sep 17 00:00:00 2001 From: shw Date: Fri, 12 May 2017 14:10:08 +0000 Subject: [PATCH] Support for firewall rule matching by uid/gid and/or user/group name value. fw-daemon prompt GUI and fw-settings now include user/uid and group/gid info. sgfw prompt GUI now displays username instead of real name. Fixed bug in parsing IP addresses as CIDR values. sgfw_rules entries can now be commented out. Upgraded bundled go-procsnitch API. --- fw-settings/definitions/RuleItem.ui | 33 ++++++- fw-settings/definitions/rule_item.go | 32 ++++++- fw-settings/rules.go | 18 +++- gnome-shell/firewall@subgraph.com/dialog.js | 21 ++++- .../firewall@subgraph.com/extension.js | 7 +- sgfw/const.go | 4 +- sgfw/dbus.go | 26 +++++- sgfw/policy.go | 4 +- sgfw/prompt.go | 33 ++++++- sgfw/rules.go | 90 ++++++++++++++++--- .../subgraph/go-procsnitch/proc_pid.go | 2 + 11 files changed, 231 insertions(+), 39 deletions(-) diff --git a/fw-settings/definitions/RuleItem.ui b/fw-settings/definitions/RuleItem.ui index 1287114..afe2ff6 100644 --- a/fw-settings/definitions/RuleItem.ui +++ b/fw-settings/definitions/RuleItem.ui @@ -31,6 +31,31 @@ 0 + + + True + False + start + 10 + + + 2 + 0 + + + + + True + False + start + 10 + + + 3 + 0 + + + True @@ -39,7 +64,7 @@ True - 2 + 4 0 @@ -60,7 +85,7 @@ - 3 + 5 0 @@ -81,7 +106,7 @@ - 5 + 7 0 @@ -103,7 +128,7 @@ - 4 + 6 0 diff --git a/fw-settings/definitions/rule_item.go b/fw-settings/definitions/rule_item.go index 4758df8..648294b 100644 --- a/fw-settings/definitions/rule_item.go +++ b/fw-settings/definitions/rule_item.go @@ -41,6 +41,30 @@ func (*defRuleItem) String() string { 0 + + + True + False + start + 10 + + + 2 + 0 + + + + + True + False + start + 10 + + + 3 + 0 + + True @@ -49,7 +73,7 @@ func (*defRuleItem) String() string { True - 2 + 4 0 @@ -70,7 +94,7 @@ func (*defRuleItem) String() string { - 3 + 5 0 @@ -91,7 +115,7 @@ func (*defRuleItem) String() string { - 5 + 7 0 @@ -113,7 +137,7 @@ func (*defRuleItem) String() string { - 4 + 6 0 diff --git a/fw-settings/rules.go b/fw-settings/rules.go index 8b7387b..dfca6c2 100644 --- a/fw-settings/rules.go +++ b/fw-settings/rules.go @@ -17,6 +17,8 @@ type ruleList struct { col1 *gtk.SizeGroup col2 *gtk.SizeGroup col3 *gtk.SizeGroup + col4 *gtk.SizeGroup + col5 *gtk.SizeGroup } type ruleRow struct { @@ -25,6 +27,8 @@ type ruleRow struct { widget *gtk.ListBoxRow gtkLabelApp *gtk.Label gtkLabelVerb *gtk.Label + gtkLabelOrigin *gtk.Label + gtkLabelPrivs *gtk.Label gtkLabelTarget *gtk.Label gtkButtonEdit *gtk.Button gtkButtonSave *gtk.Button @@ -37,6 +41,8 @@ func newRuleList(dbus *dbusObject, win *gtk.Window, list *gtk.ListBox) *ruleList rl.col1, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL) rl.col2, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL) rl.col3, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL) + rl.col4, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL) + rl.col5, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL) return rl } @@ -59,7 +65,9 @@ func (rl *ruleList) addRules(rules []sgfw.DbusRule, mode sgfw.RuleMode) { row.rl = rl rl.col1.AddWidget(row.gtkLabelApp) rl.col2.AddWidget(row.gtkLabelVerb) - rl.col3.AddWidget(row.gtkLabelTarget) + rl.col3.AddWidget(row.gtkLabelOrigin) + rl.col4.AddWidget(row.gtkLabelPrivs) + rl.col5.AddWidget(row.gtkLabelTarget) rl.list.Add(row.widget) } } @@ -73,6 +81,8 @@ func createWidget(rule *sgfw.DbusRule) *ruleRow { "grid", &grid, "app_label", &row.gtkLabelApp, "verb_label", &row.gtkLabelVerb, + "origin_label", &row.gtkLabelOrigin, + "privs_label", &row.gtkLabelPrivs, "target_label", &row.gtkLabelTarget, "edit_button", &row.gtkButtonEdit, "save_button", &row.gtkButtonSave, @@ -106,6 +116,8 @@ func (rr *ruleRow) update() { rr.gtkLabelApp.SetText(rr.rule.App) rr.gtkLabelApp.SetTooltipText(rr.rule.Path) rr.gtkLabelVerb.SetText(getVerbText(rr.rule)) + rr.gtkLabelOrigin.SetText(rr.rule.Origin) + rr.gtkLabelPrivs.SetText(rr.rule.Privs) rr.gtkLabelTarget.SetText(getTargetText(rr.rule)) } @@ -167,7 +179,9 @@ func (rr *ruleRow) onDelete() { func (rl *ruleList) remove(rr *ruleRow) { rl.col1.RemoveWidget(rr.gtkLabelApp) rl.col2.RemoveWidget(rr.gtkLabelVerb) - rl.col3.RemoveWidget(rr.gtkLabelTarget) + rl.col3.RemoveWidget(rr.gtkLabelOrigin) + rl.col4.RemoveWidget(rr.gtkLabelPrivs) + rl.col5.RemoveWidget(rr.gtkLabelTarget) rl.list.Remove(rr.widget) } diff --git a/gnome-shell/firewall@subgraph.com/dialog.js b/gnome-shell/firewall@subgraph.com/dialog.js index 1ba44d4..196392c 100644 --- a/gnome-shell/firewall@subgraph.com/dialog.js +++ b/gnome-shell/firewall@subgraph.com/dialog.js @@ -30,6 +30,7 @@ const DetailSection = new Lang.Class({ this.pid = this._addDetails("Process ID:"); this.origin = this._addDetails("Origin:"); this.user = this._addDetails("User:"); + this.group = this._addDetails("Group:"); this.optstring = this._addDetails(""); }, @@ -41,7 +42,7 @@ const DetailSection = new Lang.Class({ return msg; }, - setDetails: function(ip, path, pid, user, origin, optstring) { + setDetails: function(ip, path, pid, uid, gid, user, group, origin, optstring) { this.ipAddr.text = ip; this.path.text = path; @@ -52,7 +53,19 @@ const DetailSection = new Lang.Class({ } this.origin.text = origin; - this.user.text = user; + + if (user != "") { + this.user.text = user + " (" + uid.toString() + ")"; + } else { + this.user.text = "uid:" + uid.toString(); + } + + if (group != "") { + this.group.text = group + " (" + gid.toString() + ")"; + } else { + this.group.text = "gid:" + gid.toString(); + } + this.optstring.text = optstring } }); @@ -459,7 +472,7 @@ const PromptDialog = new Lang.Class({ } }, - update: function(application, icon, path, address, port, ip, origin, user, pid, proto, optstring, expanded, expert, action) { + update: function(application, icon, path, address, port, ip, origin, uid, gid, user, group, pid, proto, optstring, expanded, expert, action) { this._address = address; this._port = port; @@ -488,6 +501,6 @@ const PromptDialog = new Lang.Class({ } this.optionList.buttonGroup._setChecked(this.optionList.scopeToIdx(action)) - this.info.setDetails(ip, path, pid, user, origin, optstring); + this.info.setDetails(ip, path, pid, uid, gid, user, group, origin, optstring); }, }); diff --git a/gnome-shell/firewall@subgraph.com/extension.js b/gnome-shell/firewall@subgraph.com/extension.js index 981bc3f..99c60ca 100644 --- a/gnome-shell/firewall@subgraph.com/extension.js +++ b/gnome-shell/firewall@subgraph.com/extension.js @@ -51,7 +51,10 @@ const FirewallPromptInterface = ' \ \ \ \ + \ + \ \ + \ \ \ \ @@ -88,11 +91,11 @@ const FirewallPromptHandler = new Lang.Class({ }, RequestPromptAsync: function(params, invocation) { - let [app, icon, path, address, port, ip, origin, user, pid, optstring, expanded, expert, action] = params; + let [app, icon, path, address, port, ip, origin, uid, gid, user, group, pid, optstring, expanded, expert, action] = params; this._closeDialog(); this._dialog = new Dialog.PromptDialog(invocation); this._invocation = invocation; - this._dialog.update(app, icon, path, address, port, ip, origin, user, pid, "TCP", optstring, expanded, expert, action); + this._dialog.update(app, icon, path, address, port, ip, origin, uid, gid, user, group, pid, "TCP", optstring, expanded, expert, action); this._dialog.open(); }, diff --git a/sgfw/const.go b/sgfw/const.go index b6ed474..f82c280 100644 --- a/sgfw/const.go +++ b/sgfw/const.go @@ -105,7 +105,9 @@ var FilterResultValue = map[string]FilterResult{ // DbusRule struct of the rule passed to the dbus interface type DbusRule struct { ID uint32 -// Net string + Net string + Origin string + Privs string App string Path string Verb uint16 diff --git a/sgfw/dbus.go b/sgfw/dbus.go index 3797a23..7052ada 100644 --- a/sgfw/dbus.go +++ b/sgfw/dbus.go @@ -3,6 +3,7 @@ package sgfw import ( "errors" "path" + "strconv" "github.com/godbus/dbus" "github.com/godbus/dbus/introspect" @@ -93,14 +94,31 @@ func (ds *dbusServer) IsEnabled() (bool, *dbus.Error) { } func createDbusRule(r *Rule) DbusRule { -// XXX: Uncommenting will require fw-settings upgrade. -/* netstr := "" + netstr := "" if r.network != nil { netstr = r.network.String() - } */ + } + ostr := "" + if r.saddr != nil { + ostr = r.saddr.String() + } + pstr := "" + + if r.uname != "" { + pstr = r.uname + } else if r.uid >= 0 { + pstr = strconv.Itoa(r.uid) + } + if r.gname != "" { + pstr += ":" + r.gname + } else if r.gid >= 0 { + pstr += ":" + strconv.Itoa(r.gid) + } return DbusRule{ ID: uint32(r.id), -// Net: netstr, + Net: netstr, + Origin: ostr, + Privs: pstr, App: path.Base(r.policy.path), Path: r.policy.path, Verb: uint16(r.rtype), diff --git a/sgfw/policy.go b/sgfw/policy.go index 7fa3f5a..347486c 100644 --- a/sgfw/policy.go +++ b/sgfw/policy.go @@ -60,7 +60,7 @@ type pendingPkt struct { func getEmptyPInfo() *procsnitch.Info { pinfo := procsnitch.Info{} - pinfo.UID, pinfo.Pid, pinfo.ParentPid = -1, -1, -1 + pinfo.UID, pinfo.GID, pinfo.Pid, pinfo.ParentPid = -1, -1, -1, -1 pinfo.ExePath = "[unknown-exe]" pinfo.CmdLine = "[unknown-cmdline]" pinfo.FirstArg = "[unknown-arg]" @@ -294,7 +294,7 @@ func (p *Policy) removeRule(r *Rule) { func (p *Policy) filterPending(rule *Rule) { remaining := []pendingConnection{} for _, pc := range p.pendingQueue { - if rule.match(pc.src(), pc.dst(), pc.dstPort(), pc.hostname()) { + if rule.match(pc.src(), pc.dst(), pc.dstPort(), pc.hostname(), pc.procInfo().UID, pc.procInfo().GID, uidToUser(pc.procInfo().UID), gidToGroup(pc.procInfo().GID)) { log.Infof("Adding rule for: %s", rule.getString(FirewallConfig.LogRedact)) log.Noticef("%s > %s", rule.getString(FirewallConfig.LogRedact), pc.print()) if rule.rtype == RULE_ACTION_ALLOW { diff --git a/sgfw/prompt.go b/sgfw/prompt.go index 8b83167..e1ee8ae 100644 --- a/sgfw/prompt.go +++ b/sgfw/prompt.go @@ -4,6 +4,7 @@ import ( "fmt" "os/user" "strconv" + "strings" "sync" "github.com/godbus/dbus" @@ -76,7 +77,10 @@ func (p *prompter) processConnection(pc pendingConnection) { int32(pc.dstPort()), pc.dst().String(), pc.src().String(), + int32(pc.procInfo().UID), + int32(pc.procInfo().GID), uidToUser(pc.procInfo().UID), + gidToGroup(pc.procInfo().GID), int32(pc.procInfo().Pid), pc.getOptString(), FirewallConfig.PromptExpanded, @@ -91,7 +95,10 @@ func (p *prompter) processConnection(pc pendingConnection) { } if pc.src() != nil { - rule += "|" + pc.src().String() + if !strings.HasSuffix(rule, "SYSTEM") && !strings.HasSuffix(rule, "||") { + rule += "|" + } + rule += "||" + pc.src().String() } r, err := policy.parseRule(rule, false) @@ -142,6 +149,7 @@ func (p *prompter) removePolicy(policy *Policy) { } var userMap = make(map[int]string) +var groupMap = make(map[int]string) func lookupUser(uid int) string { if uid == -1 { @@ -151,7 +159,18 @@ func lookupUser(uid int) string { if err != nil { return fmt.Sprintf("%d", uid) } - return u.Name + return u.Username +} + +func lookupGroup(gid int) string { + if gid == -1 { + return "[unknown]" + } + g, err := user.LookupGroupId(strconv.Itoa(gid)) + if err != nil { + return fmt.Sprintf("%d", gid) + } + return g.Name } func uidToUser(uid int) string { @@ -163,3 +182,13 @@ func uidToUser(uid int) string { userMap[uid] = uname return uname } + +func gidToGroup(gid int) string { + gname, ok := groupMap[gid] + if ok { + return gname + } + gname = lookupGroup(gid) + groupMap[gid] = gname + return gname +} diff --git a/sgfw/rules.go b/sgfw/rules.go index c41fd27..46db5f3 100644 --- a/sgfw/rules.go +++ b/sgfw/rules.go @@ -30,6 +30,10 @@ type Rule struct { addr uint32 saddr net.IP port uint16 + uid int + gid int + uname string + gname string } func (r *Rule) String() string { @@ -75,7 +79,16 @@ func (r *Rule) AddrString(redact bool) string { type RuleList []*Rule -func (r *Rule) match(src net.IP, dst net.IP, dstPort uint16, hostname string) bool { +func (r *Rule) match(src net.IP, dst net.IP, dstPort uint16, hostname string, uid, gid int, uname, gname string) bool { + if r.uid != -1 && r.uid != uid { + return false + } else if r.gid != -1 && r.gid != gid { + return false + } else if r.uname != "" && r.uname != uname { + return false + } else if r.gname != "" && r.gname != gname { + return false + } xip := make(net.IP, 4) binary.BigEndian.PutUint32(xip, r.addr) @@ -126,7 +139,7 @@ log.Notice("! Skipping comparison against incompatible rule types: rule src = ", log.Notice("! Skipping comparison of mismatching source ips") continue } - if r.match(src, dst, dstPort, hostname) { + if r.match(src, dst, dstPort, hostname, pinfo.UID, pinfo.GID, uidToUser(pinfo.UID), gidToGroup(pinfo.GID)) { log.Notice("+ MATCH SUCCEEDED") dstStr := dst.String() if FirewallConfig.LogRedact { @@ -170,25 +183,66 @@ func (r *Rule) parse(s string) bool { r.addr = noAddress r.saddr = nil parts := strings.Split(s, "|") - if len(parts) < 2 { + if len(parts) < 4 || len(parts) > 5 { return false } - if len(parts) >= 3 && parts[2] == "SYSTEM" { + if parts[2] == "SYSTEM" { r.mode = RULE_MODE_SYSTEM + } else if parts[2] != "" { + return false + } + + if !r.parsePrivs(parts[3]) { + return false + } + +//fmt.Printf("uid = %v, gid = %v, user = %v, group = %v, hostname = %v\n", r.uid, r.gid, r.uname, r.gname, r.hostname) + + if len(parts) == 5 && len(strings.TrimSpace(parts[4])) > 0 { + r.saddr = net.ParseIP(parts[4]) - if len(parts) > 4 { - r.saddr = net.ParseIP(parts[3]) + if r.saddr == nil { + return false } - } else if len(parts) > 3 { - r.saddr = net.ParseIP(parts[3]) - } else if len(parts) > 2 { - r.saddr = net.ParseIP(parts[2]) } return r.parseVerb(parts[0]) && r.parseTarget(parts[1]) } +func (r *Rule) parsePrivs(p string) bool { + toks := strings.Split(p, ":") + if len(toks) > 2 { + return false + } + r.uid, r.gid = -1, -1 + r.uname, r.gname = "", "" + ustr := toks[0] + + uid, err := strconv.Atoi(ustr) + + if err != nil { + r.uname = ustr + } else { + r.uid = uid + } + + if len(toks) > 1 { + gstr := toks[1] + + gid, err := strconv.Atoi(gstr) + + if err != nil { + r.gname = gstr + } else { + r.gid = gid + } + + } + + return true +} + func (r *Rule) parseVerb(v string) bool { switch v { case RuleActionString[RULE_ACTION_ALLOW]: @@ -223,9 +277,14 @@ func (r *Rule) parseAddr(a string) bool { // ip := net.ParseIP(a) ip, ipnet, err := net.ParseCIDR(a) if err != nil || ip == nil { - return false + ip = net.ParseIP(a) + + if ip == nil { + return false + } + } else { + r.network = ipnet } - r.network = ipnet r.addr = binary.BigEndian.Uint32(ip.To4()) return true } @@ -332,8 +391,11 @@ func (fw *Firewall) loadRules() { for _, line := range strings.Split(string(bs), "\n") { if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { policy = fw.processPathLine(line) - } else if len(strings.TrimSpace(line)) > 0 { - processRuleLine(policy, line) + } else { + trimmed := strings.TrimSpace(line) + if len(trimmed) > 0 && trimmed[:1] != "#" { + processRuleLine(policy, trimmed) + } } } } diff --git a/vendor/github.com/subgraph/go-procsnitch/proc_pid.go b/vendor/github.com/subgraph/go-procsnitch/proc_pid.go index 6f2eab9..8338fd2 100644 --- a/vendor/github.com/subgraph/go-procsnitch/proc_pid.go +++ b/vendor/github.com/subgraph/go-procsnitch/proc_pid.go @@ -14,6 +14,7 @@ import ( // Info is a struct containing the result of a socket proc query type Info struct { UID int + GID int Pid int ParentPid int loaded bool @@ -175,6 +176,7 @@ func (pi *Info) loadProcessInfo() bool { } sys := finfo.Sys().(*syscall.Stat_t) pi.UID = int(sys.Uid) + pi.GID = int(sys.Gid) pi.ParentPid = ppid pi.ParentCmdLine = string(pbs) pi.ParentExePath = string(pexePath)