diff --git a/fw-prompt/dbus.go b/fw-prompt/dbus.go
index 9989bd2..933cb66 100644
--- a/fw-prompt/dbus.go
+++ b/fw-prompt/dbus.go
@@ -65,8 +65,7 @@ func newDbusServer() (*dbusServer, error) {
func (ds *dbusServer) RequestPrompt(application, icon, path, address string, port int32, ip, origin, proto string, uid, gid int32, username, groupname string, pid int32,
optstring string, expanded, expert bool, action int32) (int32, string, *dbus.Error) {
- log.Printf("REALLY GOT IT!")
- log.Printf("app = %s, icon = %s, path = %s, address = %s, action = %v\n", application, icon, path, address, action)
+ log.Printf("request prompt: app = %s, icon = %s, path = %s, address = %s, action = %v\n", application, icon, path, address, action)
decision := addRequest(nil, path, proto, int(pid), ip, address, int(port), int(uid), int(gid), origin, optstring)
log.Print("Waiting on decision...")
decision.Cond.L.Lock()
diff --git a/fw-prompt/fw-prompt.go b/fw-prompt/fw-prompt.go
index 0ad75ad..b39b646 100644
--- a/fw-prompt/fw-prompt.go
+++ b/fw-prompt/fw-prompt.go
@@ -513,7 +513,7 @@ func createCurrentRule() (ruleColumns, error) {
var err error = nil
if radioProcess.GetActive() {
- return rule, errors.New("Process scope is unsupported at the moment")
+ rule.Scope = int(sgfw.APPLY_PROCESS)
} else if radioParent.GetActive() {
return rule, errors.New("Parent process scope is unsupported at the moment")
} else if radioSession.GetActive() {
@@ -612,7 +612,6 @@ func getSelectedRule() (ruleColumns, int, error) {
}
rows := sel.GetSelectedRows(globalLS)
- fmt.Println("RETURNED ROWS: ", rows.Length())
if rows.Length() <= 0 {
return rule, -1, errors.New("No selection was made")
@@ -814,7 +813,6 @@ func main() {
radioParent = get_radiobutton(radioOnce, "Parent Process", false)
radioSession = get_radiobutton(radioOnce, "Session", false)
radioPermanent = get_radiobutton(radioOnce, "Permanent", false)
- radioProcess.SetSensitive(false)
radioParent.SetSensitive(false)
hbox.PackStart(lbl, false, false, 10)
hbox.PackStart(radioOnce, false, false, 5)
@@ -948,6 +946,7 @@ func main() {
editPort.SetText(strconv.Itoa(seldata.Port))
radioOnce.SetActive(true)
radioProcess.SetActive(false)
+ radioProcess.SetSensitive(seldata.Pid > 0)
radioParent.SetActive(false)
radioSession.SetActive(false)
radioPermanent.SetActive(false)
@@ -971,6 +970,7 @@ func main() {
chkUser.SetActive(false)
chkGroup.SetActive(false)
+
return
})
diff --git a/fw-settings/definitions/Dialog.ui b/fw-settings/definitions/Dialog.ui
index 18876f0..f71815e 100644
--- a/fw-settings/definitions/Dialog.ui
+++ b/fw-settings/definitions/Dialog.ui
@@ -76,7 +76,7 @@
-
+
+
+ True
+ True
+ True
+ True
+ never
+ in
+
+
+ 3
+ True
+
+
+
+
+ True
+ False
+ System
+
+
+ 3
+ True
+ False
+
+
page0
diff --git a/fw-settings/definitions/dialog.go b/fw-settings/definitions/dialog.go
index d34e2f6..85a1f25 100644
--- a/fw-settings/definitions/dialog.go
+++ b/fw-settings/definitions/dialog.go
@@ -86,7 +86,7 @@ func (*defDialog) String() string {
-
+
True
True
True
@@ -103,7 +103,7 @@ func (*defDialog) String() string {
True
False
- System
+ Process
2
@@ -111,6 +111,32 @@ func (*defDialog) String() string {
False
+
+
+ True
+ True
+ True
+ True
+ never
+ in
+
+
+ 3
+ True
+
+
+
+
+ True
+ False
+ System
+
+
+ 3
+ True
+ False
+
+
page0
@@ -272,6 +298,7 @@ func (*defDialog) String() string {
- Forever
- Session
+ - Process
- Once
diff --git a/fw-settings/main.go b/fw-settings/main.go
index 9e1136f..05fe3b8 100644
--- a/fw-settings/main.go
+++ b/fw-settings/main.go
@@ -15,6 +15,7 @@ var fwswin *gtk.Window = nil
var fwsbuilder *builder = nil
var swRulesPermanent *gtk.ScrolledWindow = nil
var swRulesSession *gtk.ScrolledWindow = nil
+var swRulesProcess *gtk.ScrolledWindow = nil
var swRulesSystem *gtk.ScrolledWindow = nil
func failDialog(parent *gtk.Window, format string, args ...interface{}) {
@@ -38,6 +39,7 @@ var repopMutex = &sync.Mutex{}
func repopulateWin() {
fmt.Println("Refreshing firewall rule list.")
repopMutex.Lock()
+ defer repopMutex.Unlock()
win := fwswin
dbus, err := newDbusObject()
@@ -57,9 +59,15 @@ func repopulateWin() {
}
swRulesSession.Remove(child)
+ child, err = swRulesProcess.GetChild()
+ if err != nil {
+ failDialog(win, "Unable to clear out process rules list display: %v", err)
+ }
+ swRulesProcess.Remove(child)
+
child, err = swRulesSystem.GetChild()
if err != nil {
- failDialog(win, "Unable to clear out session rules list display: %v", err)
+ failDialog(win, "Unable to clear out system rules list display: %v", err)
}
swRulesSystem.Remove(child)
@@ -69,10 +77,12 @@ func repopulateWin() {
boxSession, _ := gtk.ListBoxNew()
swRulesSession.Add(boxSession)
+ boxProcess, _ := gtk.ListBoxNew()
+ swRulesProcess.Add(boxProcess)
+
boxSystem, _ := gtk.ListBoxNew()
swRulesSystem.Add(boxSystem)
-
rlPermanent := newRuleList(dbus, win, boxPermanent)
if _, err := dbus.isEnabled(); err != nil {
failDialog(win, "Unable is connect to firewall daemon. Is it running?")
@@ -85,6 +95,12 @@ func repopulateWin() {
}
rlSession.loadRules(sgfw.RULE_MODE_SESSION)
+ rlProcess := newRuleList(dbus, win, boxProcess)
+ if _, err := dbus.isEnabled(); err != nil {
+ failDialog(win, "Unable is connect to firewall daemon. Is it running?")
+ }
+ rlProcess.loadRules(sgfw.RULE_MODE_PROCESS)
+
rlSystem := newRuleList(dbus, win, boxSystem)
if _, err := dbus.isEnabled(); err != nil {
failDialog(win, "Unable is connect to firewall daemon. Is it running?")
@@ -94,7 +110,6 @@ func repopulateWin() {
loadConfig(win, fwsbuilder, dbus)
// app.AddWindow(win)
win.ShowAll()
- repopMutex.Unlock()
}
func populateWin(app *gtk.Application, win *gtk.Window) {
@@ -104,6 +119,7 @@ func populateWin(app *gtk.Application, win *gtk.Window) {
"window", &win,
"swRulesPermanent", &swRulesPermanent,
"swRulesSession", &swRulesSession,
+ "swRulesProcess", &swRulesProcess,
"swRulesSystem", &swRulesSystem,
)
//win.SetIconName("security-high-symbolic")
@@ -115,6 +131,9 @@ func populateWin(app *gtk.Application, win *gtk.Window) {
boxSession, _ := gtk.ListBoxNew()
swRulesSession.Add(boxSession)
+ boxProcess, _ := gtk.ListBoxNew()
+ swRulesProcess.Add(boxProcess)
+
boxSystem, _ := gtk.ListBoxNew()
swRulesSystem.Add(boxSystem)
@@ -135,6 +154,12 @@ func populateWin(app *gtk.Application, win *gtk.Window) {
}
rlSession.loadRules(sgfw.RULE_MODE_SESSION)
+ rlProcess := newRuleList(dbus, win, boxProcess)
+ if _, err := dbus.isEnabled(); err != nil {
+ failDialog(win, "Unable is connect to firewall daemon. Is it running?")
+ }
+ rlProcess.loadRules(sgfw.RULE_MODE_PROCESS)
+
rlSystem := newRuleList(dbus, win, boxSystem)
if _, err := dbus.isEnabled(); err != nil {
failDialog(win, "Unable is connect to firewall daemon. Is it running?")
diff --git a/fw-settings/rules.go b/fw-settings/rules.go
index c0b308e..38f2397 100644
--- a/fw-settings/rules.go
+++ b/fw-settings/rules.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"strings"
+ "strconv"
"github.com/subgraph/fw-daemon/sgfw"
@@ -113,7 +114,12 @@ func createWidget(rule *sgfw.DbusRule) *ruleRow {
}
func (rr *ruleRow) update() {
- rr.gtkLabelApp.SetText(rr.rule.App)
+ if rr.rule.Mode == uint16(sgfw.RULE_MODE_PROCESS) {
+ appstr := "(" + strconv.Itoa(int(rr.rule.Pid)) + ") " + rr.rule.App
+ rr.gtkLabelApp.SetText(appstr)
+ } else {
+ rr.gtkLabelApp.SetText(rr.rule.App)
+ }
rr.gtkLabelApp.SetTooltipText(rr.rule.Path)
rr.gtkLabelVerb.SetText(getVerbText(rr.rule))
if (rr.rule.Proto == "tcp") {
diff --git a/gnome-shell/firewall@subgraph.com/dialog.js b/gnome-shell/firewall@subgraph.com/dialog.js
index 26612e8..427cad7 100644
--- a/gnome-shell/firewall@subgraph.com/dialog.js
+++ b/gnome-shell/firewall@subgraph.com/dialog.js
@@ -12,7 +12,8 @@ const Tweener = imports.ui.tweener;
const RuleScope = {
APPLY_ONCE: 0,
APPLY_SESSION: 1,
- APPLY_FOREVER: 2,
+ APPLY_PROCESS: 2,
+ APPLY_FOREVER: 3,
};
const DetailSection = new Lang.Class({
@@ -129,9 +130,13 @@ Signals.addSignalMethods(OptionListItem.prototype);
const OptionList = new Lang.Class({
Name: 'OptionList',
- _init: function() {
+ _init: function(pid_known) {
this.actor = new St.BoxLayout({vertical: true, style_class: 'fw-option-list'});
- this.buttonGroup = new ButtonGroup("Forever", "Session", "Once");
+ if (pid_known) {
+ this.buttonGroup = new ButtonGroup("Forever", "Session", "Once", "PID");
+ } else {
+ this.buttonGroup = new ButtonGroup("Forever", "Session", "Once");
+ }
this.actor.add_child(this.buttonGroup.actor);
this.items = [];
this._selected;
@@ -188,6 +193,8 @@ const OptionList = new Lang.Class({
return RuleScope.APPLY_SESSION;
case 2:
return RuleScope.APPLY_ONCE;
+ case 3:
+ return RuleScope.APPLY_PROCESS;
default:
log("unexpected scope value "+ this.buttonGroup._selected);
return RuleScope.APPLY_SESSION;
@@ -196,6 +203,8 @@ const OptionList = new Lang.Class({
scopeToIdx: function(scope) {
switch (scope) {
+ case RuleScope.APPLY_PROCESS:
+ return 3;
case RuleScope.APPLY_ONCE:
return 2;
case RuleScope.APPLY_SESSION:
@@ -413,7 +422,7 @@ const PromptDialog = new Lang.Class({
Name: 'PromptDialog',
Extends: ModalDialog.ModalDialog,
- _init: function(invocation) {
+ _init: function(invocation, pid_known) {
this.parent({ styleClass: 'fw-prompt-dialog' });
this._invocation = invocation;
this.header = new PromptDialogHeader();
@@ -426,7 +435,7 @@ const PromptDialog = new Lang.Class({
this.info = new DetailSection();
box.add_child(this.info.actor);
- this.optionList = new OptionList();
+ this.optionList = new OptionList(pid_known);
box.add_child(this.optionList.actor);
this.optionList.addOptions([
"Only PORT AND ADDRESS",
diff --git a/gnome-shell/firewall@subgraph.com/extension.js b/gnome-shell/firewall@subgraph.com/extension.js
index ff3b0c1..3499a6d 100644
--- a/gnome-shell/firewall@subgraph.com/extension.js
+++ b/gnome-shell/firewall@subgraph.com/extension.js
@@ -94,7 +94,7 @@ const FirewallPromptHandler = new Lang.Class({
RequestPromptAsync: function(params, invocation) {
let [app, icon, path, address, port, ip, origin, proto, uid, gid, user, group, pid, optstring, expanded, expert, action] = params;
// this._closeDialog();
- this._dialog = new Dialog.PromptDialog(invocation);
+ this._dialog = new Dialog.PromptDialog(invocation, (pid >= 0));
this._invocation = invocation;
this._dialog.update(app, icon, path, address, port, ip, origin, uid, gid, user, group, pid, proto, optstring, expanded, expert, action);
this._dialog.open();
diff --git a/proc-coroner/pcoroner.go b/proc-coroner/pcoroner.go
index a012af5..23e7a7d 100644
--- a/proc-coroner/pcoroner.go
+++ b/proc-coroner/pcoroner.go
@@ -18,9 +18,17 @@ type WatchProcess struct {
Stime int
}
+type CallbackEntry struct {
+ fn procCB
+ param interface{}
+}
+
type procCB func(int, interface{})
+var Callbacks []CallbackEntry
+
+
var pmutex = &sync.Mutex{}
var pidMap map[int]WatchProcess = make(map[int]WatchProcess)
@@ -53,6 +61,11 @@ func UnmonitorProcess(pid int) {
return
}
+func AddCallback(cbfunc procCB, param interface{}) {
+ cbe := CallbackEntry{cbfunc, param}
+ Callbacks = append(Callbacks, cbe)
+}
+
func MonitorThread(cbfunc procCB, param interface{}) {
for {
/* if len(pidMap) == 0 {
@@ -71,12 +84,15 @@ func MonitorThread(cbfunc procCB, param interface{}) {
if cbfunc != nil {
cbfunc(pkey, param)
}
+ for i := 0; i < len(Callbacks); i++ {
+ Callbacks[i].fn(pkey, Callbacks[i].param)
+ }
continue
}
}
- time.Sleep(2 * time.Second)
+ time.Sleep(1 * time.Second)
}
}
diff --git a/sgfw/const.go b/sgfw/const.go
index 2e25532..3bb2078 100644
--- a/sgfw/const.go
+++ b/sgfw/const.go
@@ -31,18 +31,21 @@ var RuleActionValue = map[string]RuleAction{
type RuleMode uint16
const (
RULE_MODE_SESSION RuleMode = iota
+ RULE_MODE_PROCESS
RULE_MODE_PERMANENT
RULE_MODE_SYSTEM
)
// RuleModeString is used to get a rule mode string from its id
var RuleModeString = map[RuleMode]string{
RULE_MODE_SESSION: "SESSION",
+ RULE_MODE_PROCESS: "PROCESS",
RULE_MODE_PERMANENT: "PERMANENT",
RULE_MODE_SYSTEM: "SYSTEM",
}
// RuleModeValue converts a mode string to its id
var RuleModeValue = map[string]RuleMode{
RuleModeString[RULE_MODE_SESSION]: RULE_MODE_SESSION,
+ RuleModeString[RULE_MODE_PROCESS]: RULE_MODE_PROCESS,
RuleModeString[RULE_MODE_PERMANENT]: RULE_MODE_PERMANENT,
RuleModeString[RULE_MODE_SYSTEM]: RULE_MODE_SYSTEM,
}
@@ -52,18 +55,21 @@ type FilterScope uint16
const (
APPLY_ONCE FilterScope = iota
APPLY_SESSION
+ APPLY_PROCESS
APPLY_FOREVER
)
// FilterScopeString converts a filter scope ID to its string
var FilterScopeString = map[FilterScope]string{
APPLY_ONCE: "ONCE",
APPLY_SESSION: "SESSION",
+ APPLY_PROCESS: "PROCESS",
APPLY_FOREVER: "FOREVER",
}
// FilterScopeString converts a filter scope string to its ID
var FilterScopeValue = map[string]FilterScope{
FilterScopeString[APPLY_ONCE]: APPLY_ONCE,
FilterScopeString[APPLY_SESSION]: APPLY_SESSION,
+ FilterScopeString[APPLY_PROCESS]: APPLY_PROCESS,
FilterScopeString[APPLY_FOREVER]: APPLY_FOREVER,
}
// GetFilterScopeString is used to safely return a filter scope string
@@ -108,6 +114,7 @@ type DbusRule struct {
Net string
Origin string
Proto string
+ Pid uint32
Privs string
App string
Path string
@@ -116,8 +123,8 @@ type DbusRule struct {
Mode uint16
}
-const (
+/*const (
OZ_FWRULE_WHITELIST = iota
OZ_FWRULE_BLACKLIST
OZ_FWRULE_NONE
-)
+) */
diff --git a/sgfw/dbus.go b/sgfw/dbus.go
index 87e316a..d445c01 100644
--- a/sgfw/dbus.go
+++ b/sgfw/dbus.go
@@ -8,6 +8,7 @@ import (
"github.com/godbus/dbus"
"github.com/godbus/dbus/introspect"
"github.com/op/go-logging"
+ "github.com/subgraph/fw-daemon/proc-coroner"
)
const introspectXML = `
@@ -68,6 +69,31 @@ type dbusServer struct {
prompter *prompter
}
+func DbusProcDeathCB(pid int, param interface{}) {
+ ds := param.(*dbusServer)
+ ds.fw.lock.Lock()
+ defer ds.fw.lock.Unlock()
+ done, updated := false, false
+ for !done {
+ done = true
+ for _, p := range ds.fw.policies {
+ for r := 0; r < len(p.rules); r++ {
+ if p.rules[r].pid == pid && p.rules[r].mode == RULE_MODE_PROCESS {
+ p.rules = append(p.rules[:r], p.rules[r+1:]...)
+ done = false
+ updated = true
+ log.Notice("Removed per-process firewall rule for PID: ", pid)
+ break
+ }
+ }
+ }
+ }
+
+ if updated {
+ dbusp.alertRule("Firewall removed on process death")
+ }
+}
+
func newDbusServer() (*dbusServer, error) {
conn, err := dbus.SystemBus()
if err != nil {
@@ -92,6 +118,7 @@ func newDbusServer() (*dbusServer, error) {
ds.conn = conn
ds.prompter = newPrompter(conn)
+ pcoroner.AddCallback(DbusProcDeathCB, ds)
return ds, nil
}
@@ -132,6 +159,7 @@ func createDbusRule(r *Rule) DbusRule {
Net: netstr,
Origin: ostr,
Proto: r.proto,
+ Pid: uint32(r.pid),
Privs: pstr,
App: path.Base(r.policy.path),
Path: r.policy.path,
@@ -191,6 +219,8 @@ func (ds *dbusServer) UpdateRule(rule DbusRule) *dbus.Error {
r.rtype = RuleAction(rule.Verb)
}
r.hostname = tmp.hostname
+ r.proto = tmp.proto
+ r.pid = tmp.pid
r.addr = tmp.addr
r.port = tmp.port
r.mode = RuleMode(rule.Mode)
diff --git a/sgfw/dns.go b/sgfw/dns.go
index 42eec7d..aaa1355 100644
--- a/sgfw/dns.go
+++ b/sgfw/dns.go
@@ -14,9 +14,6 @@ import (
"github.com/subgraph/fw-daemon/proc-coroner"
)
-var monitoring = false
-var mlock = sync.Mutex{}
-
type dnsEntry struct {
name string
ttl uint32
@@ -96,8 +93,7 @@ func (dc *dnsCache) processDNS(pkt *nfqueue.NFQPacket) {
}
} */
-func procDeathCallback(pid int, param interface{}) {
-// log.Warning("XXX: IN CALLBACK for pid: ", pid, " / param = ", param)
+func procDeathCallbackDNS(pid int, param interface{}) {
if pid != 0 {
cache := param.(*dnsCache)
@@ -148,15 +144,6 @@ func (dc *dnsCache) processRecordAddress(name string, answers []dnsRR, pid int)
if pid > 0 {
log.Warning("Adding process to be monitored by DNS cache: ", pid)
- if !monitoring {
- mlock.Lock()
- if !monitoring {
- monitoring = true
-// go checker(dc)
- go pcoroner.MonitorThread(procDeathCallback, dc)
- }
- mlock.Unlock()
- }
pcoroner.MonitorProcess(pid)
}
if !FirewallConfig.LogRedact {
diff --git a/sgfw/policy.go b/sgfw/policy.go
index dfeacbb..33e3730 100644
--- a/sgfw/policy.go
+++ b/sgfw/policy.go
@@ -298,6 +298,7 @@ func (p *Policy) processNewRule(r *Rule, scope FilterScope) bool {
func (p *Policy) parseRule(s string, add bool) (*Rule, error) {
log.Noticef("XXX: attempt to parse rule: |%s|\n", s)
r := new(Rule)
+ r.pid = -1
r.mode = RULE_MODE_PERMANENT
r.policy = p
if !r.parse(s) {
diff --git a/sgfw/prompt.go b/sgfw/prompt.go
index fcd154e..010dac0 100644
--- a/sgfw/prompt.go
+++ b/sgfw/prompt.go
@@ -8,6 +8,7 @@ import (
"sync"
"github.com/godbus/dbus"
+ "github.com/subgraph/fw-daemon/proc-coroner"
)
@@ -177,6 +178,10 @@ func (p *prompter) processConnection(pc pendingConnection) {
fscope := FilterScope(scope)
if fscope == APPLY_SESSION {
r.mode = RULE_MODE_SESSION
+ } else if fscope == APPLY_PROCESS {
+ r.mode = RULE_MODE_PROCESS
+ r.pid = pc.procInfo().Pid
+ pcoroner.MonitorProcess(r.pid)
}
if !policy.processNewRule(r, fscope) {
p.lock.Lock()
diff --git a/sgfw/rules.go b/sgfw/rules.go
index 8d15f41..487105e 100644
--- a/sgfw/rules.go
+++ b/sgfw/rules.go
@@ -27,6 +27,7 @@ type Rule struct {
mode RuleMode
rtype RuleAction
proto string
+ pid int
hostname string
network *net.IPNet
addr net.IP
@@ -146,7 +147,7 @@ func (rl *RuleList) filter(pkt *nfqueue.NFQPacket, src, dst net.IP, dstPort uint
result := FILTER_PROMPT
sandboxed := strings.HasPrefix(optstr, "Sandbox")
for _, r := range *rl {
-log.Notice("------------ trying match of src ", src, " against: ", r, " | ", r.saddr, " / optstr = ", optstr)
+log.Notice("------------ trying match of src ", src, " against: ", r, " | ", r.saddr, " / optstr = ", optstr, "; pid ", pinfo.Pid, " vs rule pid ", r.pid)
if r.saddr == nil && src != nil && sandboxed {
log.Notice("! Skipping comparison against incompatible rule types: rule src = ", r.saddr, " / packet src = ", src)
continue
@@ -154,6 +155,10 @@ log.Notice("! Skipping comparison against incompatible rule types: rule src = ",
log.Notice("! Skipping comparison of mismatching source ips")
continue
}
+ if r.pid >= 0 && r.pid != pinfo.Pid {
+//log.Notice("! Skipping comparison of mismatching PIDs")
+ continue
+ }
if r.match(src, dst, dstPort, hostname, getNFQProto(pkt), pinfo.UID, pinfo.GID, uidToUser(pinfo.UID), gidToGroup(pinfo.GID)) {
log.Notice("+ MATCH SUCCEEDED")
dstStr := dst.String()
diff --git a/sgfw/sgfw.go b/sgfw/sgfw.go
index 97ad4ba..95f5db3 100644
--- a/sgfw/sgfw.go
+++ b/sgfw/sgfw.go
@@ -16,6 +16,7 @@ import (
nfqueue "github.com/subgraph/go-nfnetlink/nfqueue"
// "github.com/subgraph/go-nfnetlink"
"github.com/subgraph/go-procsnitch"
+ "github.com/subgraph/fw-daemon/proc-coroner"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
@@ -227,6 +228,7 @@ func Main() {
stopChan: make(chan bool, 0),
}
ds.fw = fw
+ go pcoroner.MonitorThread(procDeathCallbackDNS, fw.dns)
fw.loadRules()