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.
shw_dev
shw 7 years ago
parent fa70c06af2
commit f47e23e706

@ -31,6 +31,31 @@
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="origin_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_right">10</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="privs_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_right">10</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<child>
<object class="GtkLabel" id="target_label">
<property name="visible">True</property>
@ -39,7 +64,7 @@
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="left_attach">4</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -60,7 +85,7 @@
</child>
</object>
<packing>
<property name="left_attach">3</property>
<property name="left_attach">5</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -81,7 +106,7 @@
</child>
</object>
<packing>
<property name="left_attach">5</property>
<property name="left_attach">7</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -103,7 +128,7 @@
</child>
</object>
<packing>
<property name="left_attach">4</property>
<property name="left_attach">6</property>
<property name="top_attach">0</property>
</packing>
</child>

@ -41,6 +41,30 @@ func (*defRuleItem) String() string {
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="origin_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_right">10</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="privs_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_right">10</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="target_label">
<property name="visible">True</property>
@ -49,7 +73,7 @@ func (*defRuleItem) String() string {
<property name="hexpand">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="left_attach">4</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -70,7 +94,7 @@ func (*defRuleItem) String() string {
</child>
</object>
<packing>
<property name="left_attach">3</property>
<property name="left_attach">5</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -91,7 +115,7 @@ func (*defRuleItem) String() string {
</child>
</object>
<packing>
<property name="left_attach">5</property>
<property name="left_attach">7</property>
<property name="top_attach">0</property>
</packing>
</child>
@ -113,7 +137,7 @@ func (*defRuleItem) String() string {
</child>
</object>
<packing>
<property name="left_attach">4</property>
<property name="left_attach">6</property>
<property name="top_attach">0</property>
</packing>
</child>

@ -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)
}

@ -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);
},
});

@ -51,7 +51,10 @@ const FirewallPromptInterface = '<node> \
<arg type="i" direction="in" name="port" /> \
<arg type="s" direction="in" name="ip" /> \
<arg type="s" direction="in" name="origin" /> \
<arg type="i" direction="in" name="uid" /> \
<arg type="i" direction="in" name="gid" /> \
<arg type="s" direction="in" name="user" /> \
<arg type="s" direction="in" name="group" /> \
<arg type="i" direction="in" name="pid" /> \
<arg type="s" direction="in" name="optstring" /> \
<arg type="b" direction="in" name="expanded" /> \
@ -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();
},

@ -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

@ -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),

@ -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 {

@ -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
}

@ -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)
}
}
}
}

@ -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)

Loading…
Cancel
Save