Added per-process (ephemeral) rule support.

(proc coroner now has support for multiple callbacks)
shw_dev
shw 7 years ago
parent af874c7395
commit 0f2b2413ea

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

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

@ -76,7 +76,7 @@
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="swRulesSystem">
<object class="GtkScrolledWindow" id="swRulesProcess">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
@ -93,7 +93,7 @@
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">System</property>
<property name="label" translatable="yes">Process</property>
</object>
<packing>
<property name="position">2</property>
@ -101,6 +101,32 @@
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="swRulesSystem">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_expand">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">System</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="name">page0</property>

@ -86,7 +86,7 @@ func (*defDialog) String() string {
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="swRulesSystem">
<object class="GtkScrolledWindow" id="swRulesProcess">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
@ -103,7 +103,7 @@ func (*defDialog) String() string {
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">System</property>
<property name="label" translatable="yes">Process</property>
</object>
<packing>
<property name="position">2</property>
@ -111,6 +111,32 @@ func (*defDialog) String() string {
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="swRulesSystem">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_expand">True</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">System</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_expand">True</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="name">page0</property>
@ -272,6 +298,7 @@ func (*defDialog) String() string {
<items>
<item id="FOREVER" translatable="yes">Forever</item>
<item id="SESSION" translatable="yes">Session</item>
<item id="PROCESS" translatable="yes">Process</item>
<item id="ONCE" translatable="yes">Once</item>
</items>
<signal name="changed" handler="on_action_combo_changed" swapped="no"/>

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save