Added new RemovePrompt DBus call to complement RequestPrompt (GUID-based prompt removal).

The addition of a rule matching multiple pending connections in fw-prompt now removes all of them.
fw-prompter now increments ref# column for identical prompt requests.
Fixed/cleaned up/updated TLSGuard code.
Added TLSGuard toggle option to fw-prompt GUI (default for SOCKS connections).
fw-prompt now displays icon of filtered application.
DBus RequestPrompt() now "works" asynchronously.
TLSGuard fixed under certain conditions but still very buggy.
Fixed some fw-prompt crash conditions with treeview mutex locking.
Fixed SOCKS connection panic condition linked to closed channel.
Cleanup of unused data structures/values.
shw_dev
Stephen Watt 7 years ago
parent a8f61a2d4e
commit 2fc7525cc7

@ -4,7 +4,6 @@ import (
"errors"
"github.com/godbus/dbus"
"log"
// "github.com/gotk3/gotk3/glib"
)
type dbusServer struct {
@ -12,27 +11,6 @@ type dbusServer struct {
run bool
}
type promptData struct {
Application string
Icon string
Path string
Address string
Port int
IP string
Origin string
Proto string
UID int
GID int
Username string
Groupname string
Pid int
Sandbox string
OptString string
Expanded bool
Expert bool
Action int
}
func newDbusServer() (*dbusServer, error) {
conn, err := dbus.SystemBus()
@ -62,10 +40,10 @@ func newDbusServer() (*dbusServer, error) {
return ds, nil
}
func (ds *dbusServer) RequestPrompt(application, icon, path, address string, port int32, ip, origin, proto string, uid, gid int32, username, groupname string, pid int32, sandbox string,
func (ds *dbusServer) RequestPrompt(guid, application, icon, path, address string, port int32, ip, origin, proto string, uid, gid int32, username, groupname string, pid int32, sandbox string,
is_socks bool, optstring string, expanded, expert bool, action int32) (int32, string, *dbus.Error) {
log.Printf("request prompt: app = %s, icon = %s, path = %s, address = %s, is_socks = %v, action = %v\n", application, icon, path, address, is_socks, action)
decision := addRequest(nil, path, proto, int(pid), ip, address, int(port), int(uid), int(gid), origin, is_socks, optstring, sandbox)
log.Printf("request prompt: app = %s, icon = %s, path = %s, address = %s / ip = %s, is_socks = %v, action = %v\n", application, icon, path, address, ip, is_socks, action)
decision := addRequest(nil, guid, path, icon, proto, int(pid), ip, address, int(port), int(uid), int(gid), origin, is_socks, optstring, sandbox)
log.Print("Waiting on decision...")
decision.Cond.L.Lock()
for !decision.Ready {
@ -73,6 +51,11 @@ func (ds *dbusServer) RequestPrompt(application, icon, path, address string, por
}
log.Print("Decision returned: ", decision.Rule)
decision.Cond.L.Unlock()
// glib.IdleAdd(func, data)
return int32(decision.Scope), decision.Rule, nil
}
func (ds *dbusServer) RemovePrompt(guid string) *dbus.Error {
log.Printf("++++++++ Cancelling prompt: %s\n", guid)
removeRequest(nil, guid)
return nil
}

@ -34,7 +34,10 @@ type decisionWaiter struct {
}
type ruleColumns struct {
nrefs int
Path string
GUID string
Icon string
Proto string
Pid int
Target string
@ -45,21 +48,25 @@ type ruleColumns struct {
Uname string
Gname string
Origin string
IsSocks bool
ForceTLS bool
Scope int
}
var userPrefs fpPreferences
var mainWin *gtk.Window
var Notebook *gtk.Notebook
var globalLS *gtk.ListStore
var globalLS *gtk.ListStore = nil
var globalTV *gtk.TreeView
var globalPromptLock = &sync.Mutex{}
var globalIcon *gtk.Image
var decisionWaiters []*decisionWaiter
var editApp, editTarget, editPort, editUser, editGroup *gtk.Entry
var comboProto *gtk.ComboBoxText
var radioOnce, radioProcess, radioParent, radioSession, radioPermanent *gtk.RadioButton
var btnApprove, btnDeny, btnIgnore *gtk.Button
var chkUser, chkGroup *gtk.CheckButton
var chkTLS, chkUser, chkGroup *gtk.CheckButton
func dumpDecisions() {
fmt.Println("XXX Total of decisions pending: ", len(decisionWaiters))
@ -306,7 +313,8 @@ func createColumn(title string, id int) *gtk.TreeViewColumn {
}
func createListStore(general bool) *gtk.ListStore {
colData := []glib.Type{glib.TYPE_INT, glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_INT, glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_INT, glib.TYPE_INT, glib.TYPE_INT, glib.TYPE_STRING, glib.TYPE_STRING}
colData := []glib.Type{glib.TYPE_INT, glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_INT,
glib.TYPE_STRING, glib.TYPE_STRING, glib.TYPE_INT, glib.TYPE_INT, glib.TYPE_INT, glib.TYPE_STRING, glib.TYPE_INT, glib.TYPE_STRING}
listStore, err := gtk.ListStoreNew(colData...)
if err != nil {
@ -316,7 +324,66 @@ func createListStore(general bool) *gtk.ListStore {
return listStore
}
func addRequest(listStore *gtk.ListStore, path, proto string, pid int, ipaddr, hostname string, port, uid, gid int, origin string, is_socks bool, optstring string, sandbox string) *decisionWaiter {
func removeRequest(listStore *gtk.ListStore, guid string) {
removed := false
globalPromptLock.Lock()
/* XXX: This is horrible. Figure out how to do this properly. */
for ridx := 0; ridx < 2000; ridx++ {
rule, _, err := getRuleByIdx(ridx)
if err != nil {
break
} else if rule.GUID == guid {
removeSelectedRule(ridx, true)
removed = true
break
}
}
globalPromptLock.Unlock()
if !removed {
log.Printf("Unexpected condition: SGFW requested prompt removal for non-existent GUID %v\n", guid)
}
}
func addRequestInc(listStore *gtk.ListStore, guid, path, icon, proto string, pid int, ipaddr, hostname string, port, uid, gid int, origin string, is_socks bool, optstring string, sandbox string) bool {
duplicated := false
globalPromptLock.Lock()
for ridx := 0; ridx < 2000; ridx++ {
/* XXX: This is horrible. Figure out how to do this properly. */
rule, iter, err := getRuleByIdx(ridx)
if err != nil {
break
// XXX: not compared: optstring/sandbox
} else if (rule.Path == path) && (rule.Proto == proto) && (rule.Pid == pid) && (rule.Target == ipaddr) && (rule.Hostname == hostname) &&
(rule.Port == port) && (rule.UID == uid) && (rule.GID == gid) && (rule.Origin == origin) && (rule.IsSocks == is_socks) {
rule.nrefs++
err := globalLS.SetValue(iter, 0, rule.nrefs)
if err != nil {
log.Print("Error creating duplicate firewall prompt entry:", err)
break
}
fmt.Println("YES REALLY DUPLICATE: ", rule.nrefs)
duplicated = true
break
}
}
globalPromptLock.Unlock()
return duplicated
}
func addRequest(listStore *gtk.ListStore, guid, path, icon, proto string, pid int, ipaddr, hostname string, port, uid, gid int, origin string, is_socks bool, optstring string, sandbox string) *decisionWaiter {
if listStore == nil {
listStore = globalLS
waitTimes := []int{1, 2, 5, 10}
@ -342,6 +409,16 @@ func addRequest(listStore *gtk.ListStore, path, proto string, pid int, ipaddr, h
log.Fatal("SGFW prompter GUI failed to load for unknown reasons")
}
if addRequestInc(listStore, guid, path, icon, proto, pid, ipaddr, hostname, port, uid, gid, origin, is_socks, optstring, sandbox) {
fmt.Println("REQUEST WAS DUPLICATE")
decision := addDecision()
toggleHover()
return decision
} else {
fmt.Println("NOT DUPLICATE")
}
globalPromptLock.Lock()
iter := listStore.Append()
if is_socks {
@ -352,24 +429,32 @@ func addRequest(listStore *gtk.ListStore, path, proto string, pid int, ipaddr, h
}
}
colVals := make([]interface{}, 11)
colVals := make([]interface{}, 14)
colVals[0] = 1
colVals[1] = path
colVals[2] = proto
colVals[3] = pid
colVals[1] = guid
colVals[2] = path
colVals[3] = icon
colVals[4] = proto
colVals[5] = pid
if ipaddr == "" {
colVals[4] = "---"
colVals[6] = "---"
} else {
colVals[4] = ipaddr
colVals[6] = ipaddr
}
colVals[5] = hostname
colVals[6] = port
colVals[7] = uid
colVals[8] = gid
colVals[9] = origin
colVals[10] = optstring
colVals[7] = hostname
colVals[8] = port
colVals[9] = uid
colVals[10] = gid
colVals[11] = origin
colVals[12] = 0
if is_socks {
colVals[12] = 1
}
colVals[13] = optstring
colNums := make([]int, len(colVals))
@ -378,6 +463,7 @@ func addRequest(listStore *gtk.ListStore, path, proto string, pid int, ipaddr, h
}
err := listStore.Set(iter, colNums, colVals)
globalPromptLock.Unlock()
if err != nil {
log.Fatal("Unable to add row:", err)
@ -495,6 +581,8 @@ func toggleHover() {
func toggleValidRuleState() {
ok := true
globalPromptLock.Lock()
if numSelections() <= 0 {
ok = false
}
@ -537,6 +625,7 @@ func toggleValidRuleState() {
btnApprove.SetSensitive(ok)
btnDeny.SetSensitive(ok)
btnIgnore.SetSensitive(ok)
globalPromptLock.Unlock()
}
func createCurrentRule() (ruleColumns, error) {
@ -579,6 +668,8 @@ func createCurrentRule() (ruleColumns, error) {
rule.UID, rule.GID = 0, 0
rule.Uname, rule.Gname = "", ""
rule.ForceTLS = chkTLS.GetActive()
/* Pid int
Origin string */
@ -586,6 +677,7 @@ func createCurrentRule() (ruleColumns, error) {
}
func clearEditor() {
globalIcon.Clear()
editApp.SetText("")
editTarget.SetText("")
editPort.SetText("")
@ -599,6 +691,7 @@ func clearEditor() {
radioPermanent.SetActive(false)
chkUser.SetActive(false)
chkGroup.SetActive(false)
chkTLS.SetActive(false)
}
func removeSelectedRule(idx int, rmdecision bool) error {
@ -634,78 +727,116 @@ func numSelections() int {
return int(rows.Length())
}
func getSelectedRule() (ruleColumns, int, error) {
// Needs to be locked by the caller
func getRuleByIdx(idx int) (ruleColumns, *gtk.TreeIter, error) {
rule := ruleColumns{}
sel, err := globalTV.GetSelection()
path, err := gtk.TreePathNewFromString(fmt.Sprintf("%d", idx))
if err != nil {
return rule, -1, err
return rule, nil, err
}
rows := sel.GetSelectedRows(globalLS)
iter, err := globalLS.GetIter(path)
if err != nil {
return rule, nil, err
}
if rows.Length() <= 0 {
return rule, -1, errors.New("No selection was made")
rule.nrefs, err = lsGetInt(globalLS, iter, 0)
if err != nil {
return rule, nil, err
}
rdata := rows.NthData(0)
lIndex, err := strconv.Atoi(rdata.(*gtk.TreePath).String())
rule.GUID, err = lsGetStr(globalLS, iter, 1)
if err != nil {
return rule, -1, err
return rule, nil, err
}
fmt.Println("lindex = ", lIndex)
path, err := gtk.TreePathNewFromString(fmt.Sprintf("%d", lIndex))
rule.Path, err = lsGetStr(globalLS, iter, 2)
if err != nil {
return rule, -1, err
return rule, nil, err
}
iter, err := globalLS.GetIter(path)
rule.Icon, err = lsGetStr(globalLS, iter, 3)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Path, err = lsGetStr(globalLS, iter, 1)
rule.Proto, err = lsGetStr(globalLS, iter, 4)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Proto, err = lsGetStr(globalLS, iter, 2)
rule.Pid, err = lsGetInt(globalLS, iter, 5)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Pid, err = lsGetInt(globalLS, iter, 3)
rule.Target, err = lsGetStr(globalLS, iter, 6)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Target, err = lsGetStr(globalLS, iter, 4)
rule.Hostname, err = lsGetStr(globalLS, iter, 7)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Hostname, err = lsGetStr(globalLS, iter, 5)
rule.Port, err = lsGetInt(globalLS, iter, 8)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.Port, err = lsGetInt(globalLS, iter, 6)
rule.UID, err = lsGetInt(globalLS, iter, 9)
if err != nil {
return rule, -1, err
return rule, nil, err
}
rule.GID, err = lsGetInt(globalLS, iter, 10)
if err != nil {
return rule, nil, err
}
rule.Origin, err = lsGetStr(globalLS, iter, 11)
if err != nil {
return rule, nil, err
}
rule.UID, err = lsGetInt(globalLS, iter, 7)
rule.IsSocks = false
is_socks, err := lsGetInt(globalLS, iter, 12)
if err != nil {
return rule, nil, err
}
if is_socks != 0 {
rule.IsSocks = true
}
return rule, iter, nil
}
// Needs to be locked by the caller
func getSelectedRule() (ruleColumns, int, error) {
rule := ruleColumns{}
sel, err := globalTV.GetSelection()
if err != nil {
return rule, -1, err
}
rule.GID, err = lsGetInt(globalLS, iter, 8)
rows := sel.GetSelectedRows(globalLS)
if rows.Length() <= 0 {
return rule, -1, errors.New("No selection was made")
}
rdata := rows.NthData(0)
lIndex, err := strconv.Atoi(rdata.(*gtk.TreePath).String())
if err != nil {
return rule, -1, err
}
rule.Origin, err = lsGetStr(globalLS, iter, 9)
fmt.Println("lindex = ", lIndex)
rule, _, err = getRuleByIdx(lIndex)
if err != nil {
return rule, -1, err
}
@ -811,10 +942,18 @@ func main() {
editbox := get_vbox()
hbox := get_hbox()
lbl := get_label("Application path:")
globalIcon, err = gtk.ImageNew()
if err != nil {
log.Fatal("Unable to create image:", err)
}
// globalIcon.SetFromIconName("firefox", gtk.ICON_SIZE_DND)
editApp = get_entry("")
editApp.Connect("changed", toggleValidRuleState)
hbox.PackStart(lbl, false, false, 10)
hbox.PackStart(editApp, true, true, 50)
hbox.PackStart(editApp, true, true, 10)
hbox.PackStart(globalIcon, false, false, 10)
editbox.PackStart(hbox, false, false, 5)
hbox = get_hbox()
@ -842,7 +981,9 @@ func main() {
radioSession = get_radiobutton(radioOnce, "Session", false)
radioPermanent = get_radiobutton(radioOnce, "Permanent", false)
radioParent.SetSensitive(false)
hbox.PackStart(lbl, false, false, 10)
chkTLS = get_checkbox("Require TLS", false)
hbox.PackStart(chkTLS, false, false, 10)
hbox.PackStart(lbl, false, false, 20)
hbox.PackStart(radioOnce, false, false, 5)
hbox.PackStart(radioProcess, false, false, 5)
hbox.PackStart(radioParent, false, false, 5)
@ -872,16 +1013,31 @@ func main() {
box.PackStart(scrollbox, false, true, 5)
tv.AppendColumn(createColumn("#", 0))
tv.AppendColumn(createColumn("Path", 1))
tv.AppendColumn(createColumn("Protocol", 2))
tv.AppendColumn(createColumn("PID", 3))
tv.AppendColumn(createColumn("IP Address", 4))
tv.AppendColumn(createColumn("Hostname", 5))
tv.AppendColumn(createColumn("Port", 6))
tv.AppendColumn(createColumn("UID", 7))
tv.AppendColumn(createColumn("GID", 8))
tv.AppendColumn(createColumn("Origin", 9))
tv.AppendColumn(createColumn("Details", 10))
guidcol := createColumn("GUID", 1)
guidcol.SetVisible(false)
tv.AppendColumn(guidcol)
tv.AppendColumn(createColumn("Path", 2))
icol := createColumn("Icon", 3)
icol.SetVisible(false)
tv.AppendColumn(icol)
tv.AppendColumn(createColumn("Protocol", 4))
tv.AppendColumn(createColumn("PID", 5))
tv.AppendColumn(createColumn("IP Address", 6))
tv.AppendColumn(createColumn("Hostname", 7))
tv.AppendColumn(createColumn("Port", 8))
tv.AppendColumn(createColumn("UID", 9))
tv.AppendColumn(createColumn("GID", 10))
tv.AppendColumn(createColumn("Origin", 11))
scol := createColumn("Is SOCKS", 12)
scol.SetVisible(false)
tv.AppendColumn(scol)
tv.AppendColumn(createColumn("Details", 13))
listStore := createListStore(true)
globalLS = listStore
@ -889,23 +1045,33 @@ func main() {
tv.SetModel(listStore)
btnApprove.Connect("clicked", func() {
// globalPromptLock.Lock()
rule, idx, err := getSelectedRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Error occurred processing request: " + err.Error())
return
}
rule, err = createCurrentRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Error occurred constructing new rule: " + err.Error())
return
}
fmt.Println("rule = ", rule)
rulestr := "ALLOW|" + rule.Proto + ":" + rule.Target + ":" + strconv.Itoa(rule.Port)
rulestr := "ALLOW"
if rule.ForceTLS {
rulestr += "_TLSONLY"
}
rulestr += "|" + rule.Proto + ":" + rule.Target + ":" + strconv.Itoa(rule.Port)
fmt.Println("RULESTR = ", rulestr)
makeDecision(idx, rulestr, int(rule.Scope))
fmt.Println("Decision made.")
// globalPromptLock.Unlock()
err = removeSelectedRule(idx, true)
if err == nil {
clearEditor()
@ -915,14 +1081,17 @@ func main() {
})
btnDeny.Connect("clicked", func() {
// globalPromptLock.Lock()
rule, idx, err := getSelectedRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Error occurred processing request: " + err.Error())
return
}
rule, err = createCurrentRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Error occurred constructing new rule: " + err.Error())
return
}
@ -932,6 +1101,7 @@ func main() {
fmt.Println("RULESTR = ", rulestr)
makeDecision(idx, rulestr, int(rule.Scope))
fmt.Println("Decision made.")
// globalPromptLock.Unlock()
err = removeSelectedRule(idx, true)
if err == nil {
clearEditor()
@ -941,14 +1111,17 @@ func main() {
})
btnIgnore.Connect("clicked", func() {
// globalPromptLock.Lock()
_, idx, err := getSelectedRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Error occurred processing request: " + err.Error())
return
}
makeDecision(idx, "", 0)
fmt.Println("Decision made.")
// globalPromptLock.Unlock()
err = removeSelectedRule(idx, true)
if err == nil {
clearEditor()
@ -959,14 +1132,22 @@ func main() {
// tv.SetActivateOnSingleClick(true)
tv.Connect("row-activated", func() {
// globalPromptLock.Lock()
seldata, _, err := getSelectedRule()
if err != nil {
// globalPromptLock.Unlock()
promptError("Unexpected error reading selected rule: " + err.Error())
return
}
editApp.SetText(seldata.Path)
if seldata.Icon != "" {
globalIcon.SetFromIconName(seldata.Icon, gtk.ICON_SIZE_DND)
} else {
globalIcon.Clear()
}
if seldata.Hostname != "" {
editTarget.SetText(seldata.Hostname)
} else {
@ -981,6 +1162,7 @@ func main() {
radioSession.SetActive(false)
radioPermanent.SetActive(false)
comboProto.SetActiveID(seldata.Proto)
chkTLS.SetActive(seldata.IsSocks)
if seldata.Uname != "" {
editUser.SetText(seldata.Uname)
@ -1001,6 +1183,7 @@ func main() {
chkUser.SetActive(false)
chkGroup.SetActive(false)
// globalPromptLock.Unlock()
return
})

@ -52,6 +52,9 @@ type pendingConnection interface {
drop()
setPrompting(bool)
getPrompting() bool
setPrompter(*dbusObjectP)
getPrompter() *dbusObjectP
getGUID() string
print() string
}
@ -62,6 +65,23 @@ type pendingPkt struct {
pinfo *procsnitch.Info
optstring string
prompting bool
prompter *dbusObjectP
guid string
}
/* Not a *REAL* GUID */
func genGUID() string {
frnd, err := os.Open("/dev/urandom")
if err != nil {
log.Fatal("Error reading random data source:", err)
}
rndb := make([]byte, 16)
frnd.Read(rndb)
frnd.Close()
guid := fmt.Sprintf("%x-%x-%x-%x", rndb[0:4], rndb[4:8], rndb[8:12], rndb[12:])
return guid
}
func getEmptyPInfo() *procsnitch.Info {
@ -165,6 +185,22 @@ func (pp *pendingPkt) drop() {
pp.pkt.Accept()
}
func (pp *pendingPkt) setPrompter(val *dbusObjectP) {
pp.prompter = val
}
func (pp *pendingPkt) getPrompter() *dbusObjectP {
return pp.prompter
}
func (pp *pendingPkt) getGUID() string {
if pp.guid == "" {
pp.guid = genGUID()
}
return pp.guid
}
func (pp *pendingPkt) getPrompting() bool {
return pp.prompting
}
@ -265,7 +301,7 @@ func (p *Policy) processPacket(pkt *nfqueue.NFQPacket, pinfo *procsnitch.Info, o
case FILTER_ALLOW:
pkt.Accept()
case FILTER_PROMPT:
p.processPromptResult(&pendingPkt{pol: p, name: name, pkt: pkt, pinfo: pinfo, optstring: optstr, prompting: false})
p.processPromptResult(&pendingPkt{pol: p, name: name, pkt: pkt, pinfo: pinfo, optstring: optstr, prompter: nil, prompting: false})
default:
log.Warningf("Unexpected filter result: %d", result)
}
@ -327,6 +363,7 @@ func (p *Policy) processNewRule(r *Rule, scope FilterScope) bool {
if scope != APPLY_ONCE {
p.rules = append(p.rules, r)
}
fmt.Println("----------------------- processNewRule()")
p.filterPending(r)
if len(p.pendingQueue) == 0 {
p.promptInProgress = false
@ -370,6 +407,17 @@ func (p *Policy) filterPending(rule *Rule) {
remaining := []pendingConnection{}
for _, pc := range p.pendingQueue {
if rule.match(pc.src(), pc.dst(), pc.dstPort(), pc.hostname(), pc.proto(), pc.procInfo().UID, pc.procInfo().GID, uidToUser(pc.procInfo().UID), gidToGroup(pc.procInfo().GID)) {
prompter := pc.getPrompter()
if prompter == nil {
fmt.Println("-------- prompter = NULL")
} else {
fmt.Println("---------- could send prompter")
call := prompter.Call("com.subgraph.FirewallPrompt.RemovePrompt", 0, pc.getGUID())
fmt.Println("CAAAAAAAAAAAAAAALL = ", call)
}
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 {

@ -18,7 +18,9 @@ var DoMultiPrompt = true
const MAX_PROMPTS = 5
var outstandingPrompts = 0
var outstandingPromptChans [](chan *dbus.Call)
var promptLock = &sync.Mutex{}
var promptChanLock = &sync.Mutex{}
func newPrompter(conn *dbus.Conn) *prompter {
p := new(prompter)
@ -37,6 +39,30 @@ type prompter struct {
policyQueue []*Policy
}
func saveChannel(ch chan *dbus.Call, add bool, do_close bool) {
promptChanLock.Lock()
if add {
outstandingPromptChans = append(outstandingPromptChans, ch)
} else {
for idx, och := range outstandingPromptChans {
if och == ch {
outstandingPromptChans = append(outstandingPromptChans[:idx], outstandingPromptChans[idx+1:]...)
break
}
}
}
if !add && do_close {
close(ch)
}
promptChanLock.Unlock()
return
}
func (p *prompter) prompt(policy *Policy) {
p.lock.Lock()
defer p.lock.Unlock()
@ -125,10 +151,29 @@ func processReturn(pc pendingConnection) {
pc.setPrompting(false)
}
func alertChannel(chidx int, scope int32, rule string) {
defer func() {
if r := recover(); r != nil {
log.Warning("SGFW recovered from panic while delivering out of band rule:", r)
}
}()
promptData := make([]interface{}, 3)
promptData[0] = scope
promptData[1] = rule
promptData[2] = 666
outstandingPromptChans[chidx] <- &dbus.Call{Body: promptData}
}
func (p *prompter) processConnection(pc pendingConnection) {
var scope int32
var rule string
if pc.getPrompter() == nil {
pc.setPrompter(&dbusObjectP{p.dbusObj})
}
if DoMultiPrompt {
defer processReturn(pc)
}
@ -144,10 +189,14 @@ func (p *prompter) processConnection(pc pendingConnection) {
if pc.dst() != nil {
dststr = pc.dst().String()
} else {
dststr = addr + " (proxy to resolve)"
dststr = addr + " (via proxy resolver)"
}
call := p.dbusObj.Call("com.subgraph.FirewallPrompt.RequestPrompt", 0,
callChan := make(chan *dbus.Call, 10)
saveChannel(callChan, true, false)
fmt.Println("# outstanding prompt chans = ", len(outstandingPromptChans))
p.dbusObj.Go("com.subgraph.FirewallPrompt.RequestPrompt", 0, callChan,
pc.getGUID(),
policy.application,
policy.icon,
policy.path,
@ -167,13 +216,61 @@ func (p *prompter) processConnection(pc pendingConnection) {
FirewallConfig.PromptExpanded,
FirewallConfig.PromptExpert,
int32(FirewallConfig.DefaultActionID))
err := call.Store(&scope, &rule)
select {
case call := <-callChan:
if call.Err != nil {
fmt.Println("Error reading DBus channel (accepting packet): ", call.Err)
policy.removePending(pc)
pc.accept()
saveChannel(callChan, false, true)
time.Sleep(1 * time.Second)
return
}
if len(call.Body) != 2 {
log.Warning("SGFW got back response in unrecognized format, len = ", len(call.Body))
saveChannel(callChan, false, true)
if (len(call.Body) == 3) && (call.Body[2] == 666) {
fmt.Printf("+++++++++ AWESOME: %v | %v | %v\n", call.Body[0], call.Body[1], call.Body[2])
scope = call.Body[0].(int32)
rule = call.Body[1].(string)
}
return
}
fmt.Printf("DBUS GOT BACK: %v, %v\n", call.Body[0], call.Body[1])
scope = call.Body[0].(int32)
rule = call.Body[1].(string)
}
saveChannel(callChan, false, true)
// Try alerting every other channel
promptData := make([]interface{}, 3)
promptData[0] = scope
promptData[1] = rule
promptData[2] = 666
promptChanLock.Lock()
fmt.Println("# channels to alert: ", len(outstandingPromptChans))
for chidx, _ := range outstandingPromptChans {
alertChannel(chidx, scope, rule)
// ch <- &dbus.Call{Body: promptData}
}
promptChanLock.Unlock()
/* err := call.Store(&scope, &rule)
if err != nil {
log.Warningf("Error sending dbus RequestPrompt message: %v", err)
policy.removePending(pc)
pc.drop()
return
}
} */
// the prompt sends:
// ALLOW|dest or DENY|dest

@ -56,6 +56,8 @@ type pendingSocksConnection struct {
pinfo *procsnitch.Info
verdict chan int
prompting bool
prompter *dbusObjectP
guid string
optstr string
}
@ -107,8 +109,11 @@ func (sc *pendingSocksConnection) deliverVerdict(v int) {
}
}()
if sc.verdict != nil {
sc.verdict <- v
close(sc.verdict)
sc.verdict = nil
}
}
func (sc *pendingSocksConnection) accept() { sc.deliverVerdict(socksVerdictAccept) }
@ -119,6 +124,18 @@ func (sc *pendingSocksConnection) acceptTLSOnly() { sc.deliverVerdict(socksVerdi
func (sc *pendingSocksConnection) drop() { sc.deliverVerdict(socksVerdictDrop) }
func (sc *pendingSocksConnection) setPrompter(val *dbusObjectP) { sc.prompter = val }
func (sc *pendingSocksConnection) getPrompter() *dbusObjectP { return sc.prompter }
func (sc *pendingSocksConnection) getGUID() string {
if sc.guid == "" {
sc.guid = genGUID()
}
return sc.guid
}
func (sc *pendingSocksConnection) getPrompting() bool { return sc.prompting }
func (sc *pendingSocksConnection) setPrompting(val bool) { sc.prompting = val }
@ -364,6 +381,7 @@ func (c *socksChainSession) filterConnect() (bool, bool) {
pinfo: pinfo,
verdict: make(chan int),
prompting: false,
prompter: nil,
optstr: optstr,
}
policy.processPromptResult(pending)
@ -409,7 +427,7 @@ func (c *socksChainSession) forwardTraffic(tls bool) {
if c.pinfo.Sandbox != "" {
log.Errorf("TLSGuard violation: Dropping traffic from %s (sandbox: %s) to %s: %v", c.pinfo.ExePath, c.pinfo.Sandbox, c.req.Addr.addrStr, err)
} else {
log.Errorf("TLSGuard violation: Dropping traffic from %s (unsandboxed) to %s: %v", c.pinfo.ExePath, c.req.Addr.addrStr, err)
log.Errorf("TLSGuard violation: Dropping traffic from %s (un-sandboxed) to %s: %v", c.pinfo.ExePath, c.req.Addr.addrStr, err)
}
return
} else {

@ -3,104 +3,182 @@ package sgfw
import (
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"time"
)
func TLSGuard(conn, conn2 net.Conn, fqdn string) error {
// Should this be a requirement?
// if strings.HasSuffix(request.DestAddr.FQDN, "onion") {
const TLSGUARD_READ_TIMEOUT = 5 * time.Second
const TLSGUARD_MIN_TLS_VER_MAJ = 3
const TLSGUARD_MIN_TLS_VER_MIN = 1
handshakeByte, err := readNBytes(conn, 1)
if err != nil {
return err
const SSL3_RT_CHANGE_CIPHER_SPEC = 20
const SSL3_RT_ALERT = 21
const SSL3_RT_HANDSHAKE = 22
const SSL3_RT_APPLICATION_DATA = 23
const SSL3_MT_SERVER_HELLO = 2
const SSL3_MT_CERTIFICATE = 11
const SSL3_MT_CERTIFICATE_REQUEST = 13
const SSL3_MT_SERVER_DONE = 14
type connReader struct {
client bool
data []byte
rtype int
err error
}
if handshakeByte[0] != 0x16 {
return errors.New("Blocked client from attempting non-TLS connection")
func connectionReader(conn net.Conn, is_client bool, c chan connReader, done chan bool) {
var ret_error error = nil
buffered := []byte{}
mlen := 0
rtype := 0
stage := 1
for {
if ret_error != nil {
cr := connReader{client: is_client, data: nil, rtype: 0, err: ret_error}
c <- cr
break
}
vers, err := readNBytes(conn, 2)
if err != nil {
return err
select {
case <-done:
fmt.Println("++ DONE: ", is_client)
if len(buffered) > 0 {
//fmt.Println("++ DONE BUT DISPOSING OF BUFFERED DATA")
c <- connReader{client: is_client, data: buffered, rtype: 0, err: nil}
}
length, err := readNBytes(conn, 2)
c <- connReader{client: is_client, data: nil, rtype: 0, err: nil}
return
default:
if stage == 1 {
header := make([]byte, 5)
conn.SetReadDeadline(time.Now().Add(TLSGUARD_READ_TIMEOUT))
_, err := io.ReadFull(conn, header)
conn.SetReadDeadline(time.Time{})
if err != nil {
return err
ret_error = err
continue
}
ffslen := int(int(length[0])<<8 | int(length[1]))
if int(header[1]) < TLSGUARD_MIN_TLS_VER_MAJ {
ret_error = errors.New("TLS protocol major version less than expected minimum")
continue
} else if int(header[2]) < TLSGUARD_MIN_TLS_VER_MIN {
ret_error = errors.New("TLS protocol minor version less than expected minimum")
continue
}
ffs, err := readNBytes(conn, ffslen)
rtype = int(header[0])
mlen = int(int(header[3])<<8 | int(header[4]))
fmt.Printf("TLS data chunk header read: type = %#x, maj = %v, min = %v, len = %v\n", rtype, header[1], header[2], mlen)
buffered = header
stage++
} else if stage == 2 {
remainder := make([]byte, mlen)
conn.SetReadDeadline(time.Now().Add(TLSGUARD_READ_TIMEOUT))
_, err := io.ReadFull(conn, remainder)
conn.SetReadDeadline(time.Time{})
if err != nil {
return err
ret_error = err
continue
}
// Transmit client hello
conn2.Write(handshakeByte)
conn2.Write(vers)
conn2.Write(length)
conn2.Write(ffs)
buffered = append(buffered, remainder...)
fmt.Printf("------- CHUNK READ: client: %v, err = %v, bytes = %v\n", is_client, err, len(buffered))
cr := connReader{client: is_client, data: buffered, rtype: rtype, err: err}
c <- cr
// Read ServerHello
bytesRead := 0
var s byte // 0x0e is done
var responseBuf []byte = []byte{}
valid := false
sendToClient := false
buffered = []byte{}
rtype = 0
mlen = 0
stage = 1
}
for sendToClient == false {
// Handshake byte
serverhandshakeByte, err := readNBytes(conn2, 1)
if err != nil {
return nil
}
responseBuf = append(responseBuf, serverhandshakeByte[0])
bytesRead += 1
}
if serverhandshakeByte[0] != 0x16 {
return errors.New("Expected TLS server handshake byte was not received")
}
// Protocol version, 2 bytes
serverProtocolVer, err := readNBytes(conn2, 2)
if err != nil {
return err
func TLSGuard(conn, conn2 net.Conn, fqdn string) error {
x509Valid := false
ndone := 0
// Should this be a requirement?
// if strings.HasSuffix(request.DestAddr.FQDN, "onion") {
//conn client
//conn2 server
fmt.Println("-------- STARTING HANDSHAKE LOOP")
crChan := make(chan connReader)
dChan := make(chan bool, 10)
go connectionReader(conn, true, crChan, dChan)
go connectionReader(conn2, false, crChan, dChan)
select_loop:
for {
if ndone == 2 {
fmt.Println("DONE channel got both notifications. Terminating loop.")
close(dChan)
close(crChan)
break
}
bytesRead += 2
responseBuf = append(responseBuf, serverProtocolVer...)
select {
case cr := <-crChan:
other := conn
// Record length, 2 bytes
serverRecordLen, err := readNBytes(conn2, 2)
if err != nil {
return err
if cr.client {
other = conn2
}
bytesRead += 2
responseBuf = append(responseBuf, serverRecordLen...)
serverRecordLenInt := int(int(serverRecordLen[0])<<8 | int(serverRecordLen[1]))
fmt.Printf("++++ SELECT: %v, %v, %v\n", cr.client, cr.err, len(cr.data))
if cr.err == nil && cr.data == nil {
fmt.Println("DONE channel notification received")
ndone++
continue
}
// Record type byte
serverMsg, err := readNBytes(conn2, serverRecordLenInt)
if err != nil {
return err
if cr.err == nil {
if cr.rtype == SSL3_RT_CHANGE_CIPHER_SPEC || cr.rtype == SSL3_RT_APPLICATION_DATA ||
cr.rtype == SSL3_RT_ALERT {
// fmt.Println("OTHER DATA; PASSING THRU")
if cr.rtype == SSL3_RT_ALERT {
fmt.Println("ALERT = ", cr.data)
}
other.Write(cr.data)
continue
} else if cr.client {
other.Write(cr.data)
continue
} else if cr.rtype != SSL3_RT_HANDSHAKE {
return errors.New(fmt.Sprintf("Expected TLS server handshake byte was not received [%#x vs 0x16]", cr.rtype))
}
serverMsg := cr.data[5:]
s := serverMsg[0]
fmt.Printf("s = %#x\n", s)
bytesRead += len(serverMsg)
responseBuf = append(responseBuf, serverMsg...)
s = serverMsg[0]
if s > 0x22 {
fmt.Println("WTF: ", cr.data)
}
if s == SSL3_MT_CERTIFICATE {
fmt.Println("HMM")
// Message len, 3 bytes
serverMessageLen := serverMsg[1:4]
serverMessageLenInt := int(int(serverMessageLen[0])<<16 | int(serverMessageLen[1])<<8 | int(serverMessageLen[2]))
// serverHelloBody, err := readNBytes(conn2, serverMessageLenInt)
// fmt.Printf("chunk len = %v, serverMsgLen = %v, slint = %v\n", len(chunk), len(serverMsg), serverMessageLenInt)
if len(serverMsg) < serverMessageLenInt {
return errors.New(fmt.Sprintf("len(serverMsg) %v < serverMessageLenInt %v!\n", len(serverMsg), serverMessageLenInt))
}
serverHelloBody := serverMsg[4 : 4+serverMessageLenInt]
if s == 0x0b {
certChainLen := int(int(serverHelloBody[0])<<16 | int(serverHelloBody[1])<<8 | int(serverHelloBody[2]))
remaining := certChainLen
pos := serverHelloBody[3:certChainLen]
@ -108,6 +186,7 @@ func TLSGuard(conn, conn2 net.Conn, fqdn string) error {
// var certChain []*x509.Certificate
var verifyOptions x509.VerifyOptions
//fqdn = "www.reddit.com"
if fqdn != "" {
verifyOptions.DNSName = fqdn
}
@ -134,46 +213,69 @@ func TLSGuard(conn, conn2 net.Conn, fqdn string) error {
pos = pos[3+certLen:]
}
}
verifyOptions.Intermediates = pool
_, err = c.Verify(verifyOptions)
verifyOptions.Intermediates = pool
fmt.Println("ATTEMPTING TO VERIFY: ", fqdn)
_, err := c.Verify(verifyOptions)
fmt.Println("ATTEMPTING TO VERIFY RESULT: ", err)
if err != nil {
return err
} else {
valid = true
x509Valid = true
}
// else if s == 0x0d { fmt.Printf("found a client cert request, sending buf to client\n") }
} else if s == 0x0e {
sendToClient = true
} else if s == 0x0d {
sendToClient = true
}
// fmt.Printf("Version bytes: %x %x\n", responseBuf[1], responseBuf[2])
// fmt.Printf("Len bytes: %x %x\n", responseBuf[3], responseBuf[4])
// fmt.Printf("Message type: %x\n", responseBuf[5])
// fmt.Printf("Message len: %x %x %x\n", responseBuf[6], responseBuf[7], responseBuf[8])
// fmt.Printf("Message body: %v\n", responseBuf[9:])
conn.Write(responseBuf)
responseBuf = []byte{}
other.Write(cr.data)
if x509Valid || (s == SSL3_MT_SERVER_DONE) || (s == SSL3_MT_CERTIFICATE_REQUEST) {
fmt.Println("BREAKING OUT OF LOOP 1")
dChan <- true
fmt.Println("BREAKING OUT OF LOOP 2")
break select_loop
}
if !valid {
return errors.New("Unknown error: TLS connection could not be validated")
// fmt.Printf("Sending chunk of type %d to client.\n", s)
} else if cr.err != nil {
ndone++
if cr.client {
fmt.Println("Client read error: ", cr.err)
} else {
fmt.Println("Server read error: ", cr.err)
}
return nil
return cr.err
}
func readNBytes(conn net.Conn, numBytes int) ([]byte, error) {
res := make([]byte, 0)
temp := make([]byte, 1)
for i := 0; i < numBytes; i++ {
_, err := io.ReadAtLeast(conn, temp, 1)
if err != nil {
return res, err
}
res = append(res, temp[0])
}
return res, nil
fmt.Println("WAITING; ndone = ", ndone)
for ndone < 2 {
fmt.Println("WAITING; ndone = ", ndone)
select {
case cr := <-crChan:
fmt.Printf("CHAN DATA: %v, %v, %v\n", cr.client, cr.err, len(cr.data))
if cr.err != nil || cr.data == nil {
ndone++
} else if cr.client {
conn2.Write(cr.data)
} else if !cr.client {
conn.Write(cr.data)
}
}
}
fmt.Println("______ ndone = 2\n")
// dChan <- true
close(dChan)
if !x509Valid {
return errors.New("Unknown error: TLS connection could not be validated")
}
return nil
}

Loading…
Cancel
Save