From cadb859dcefcb8736cbaa5196e5bbbb77d9a6552 Mon Sep 17 00:00:00 2001 From: shw Date: Thu, 13 Apr 2017 22:13:56 +0000 Subject: [PATCH] Added ephemeral oz sandbox/fw-daemon rules that can be updated via IPC connection. fw-daemon prompter is now updated with source address of originating packet. Fixed bug in decoding DNS data. Packets are dropped properly (by marking and then calling Accept()). --- fw-ozcli/fw-ozcli.go | 90 ++++++++++++++++++ sgfw/const.go | 6 ++ sgfw/dns.go | 3 +- sgfw/ipc.go | 187 +++++++++++++++++++++++++++++++++++++ sgfw/policy.go | 77 +++++++++++++-- sgfw/prompt.go | 2 +- sgfw/sgfw.go | 3 + sgfw/socks_server_chain.go | 8 +- 8 files changed, 365 insertions(+), 11 deletions(-) create mode 100644 fw-ozcli/fw-ozcli.go create mode 100644 sgfw/ipc.go diff --git a/fw-ozcli/fw-ozcli.go b/fw-ozcli/fw-ozcli.go new file mode 100644 index 0000000..c7ae10c --- /dev/null +++ b/fw-ozcli/fw-ozcli.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "flag" + "strconv" + "io" + "log" + "net" +) + +const ReceiverSocketPath = "/tmp/fwoz.sock" + +func reader(r io.Reader) { + buf := make([]byte, 1024) + + for { + n, err := r.Read(buf[:]) + if err != nil { + return + } + fmt.Println(string(buf[0:n])) + } +} + +func main() { + dump := flag.Bool("d", false, "dump current oz-fw rules") + whitelist := flag.Bool("w", false, "submit whitelist rule") + blacklist := flag.Bool("b", false, "submit blacklist rule") + src := flag.String("src", "", "source IP address") + dst := flag.String("dst", "", "destination IP address") + port := flag.Int("port", 0, "destination port number") + rm := flag.Bool("rm", false, "remove entry from rules (default is add)") + + flag.Parse() + + if !*dump { + + if *src == "" { + log.Fatal("Error: must specify source address with -src") + } else if *dst == "" { + log.Fatal("Error: must specify destination address with -dst") + } else if *port == 0 { + log.Fatal("Error: must specify destination port with -port") + } else if *port <= 0 || *port > 65535 { + log.Fatal("Error: invalid port was specified") + } else if !*whitelist && !*blacklist { + log.Fatal("Error: -w or -b must be specified to whitelist or blacklist entry") + } else if *whitelist && *blacklist { + log.Fatal("Error: -w and -b cannot be specified together") + } + + } else { + fmt.Println("Attempting to dump active rule list.") + } + + c, err := net.Dial("unix", ReceiverSocketPath) + if err != nil { + log.Fatal("Could not establish connection to listener:", err) + } + + defer c.Close() + + if *dump { + c.Write([]byte("dump\n")) + reader(c) + fmt.Println("Done.") + } else { + reqstr := "" + + if *rm { + reqstr += "remove " + } else { + reqstr += "add " + } + + if *whitelist { + reqstr += "whitelist" + } else { + reqstr += "blacklist" + } + + reqstr += " " + *src + " " + *dst + " " + strconv.Itoa(*port) + "\n" + c.Write([]byte(reqstr)) + reader(c) + fmt.Println("Done.") + } + +} + diff --git a/sgfw/const.go b/sgfw/const.go index f683647..10d6cf5 100644 --- a/sgfw/const.go +++ b/sgfw/const.go @@ -111,3 +111,9 @@ type DbusRule struct { Target string Mode uint16 } + +const ( + OZ_FWRULE_WHITELIST = iota + OZ_FWRULE_BLACKLIST + OZ_FWRULE_NONE +) diff --git a/sgfw/dns.go b/sgfw/dns.go index 4ba3ba3..8687a95 100644 --- a/sgfw/dns.go +++ b/sgfw/dns.go @@ -7,6 +7,7 @@ import ( // "github.com/subgraph/go-nfnetlink" "github.com/google/gopacket" + "github.com/google/gopacket/layers" ) type dnsCache struct { @@ -24,7 +25,7 @@ func newDNSCache() *dnsCache { func (dc *dnsCache) processDNS(pkt gopacket.Packet) { dns := &dnsMsg{} - if !dns.Unpack(pkt.ApplicationLayer().Payload()) { + if !dns.Unpack(pkt.Layer(layers.LayerTypeDNS).LayerContents()) { log.Warning("Failed to Unpack DNS message") return } diff --git a/sgfw/ipc.go b/sgfw/ipc.go new file mode 100644 index 0000000..e66e3ba --- /dev/null +++ b/sgfw/ipc.go @@ -0,0 +1,187 @@ +package sgfw + +import ( + "fmt" + "net" + "os" + "bufio" + "strings" + "strconv" +) + +const ReceiverSocketPath = "/tmp/fwoz.sock" + + +func canAddRule(rule sandboxRule) bool { + + for i := 0; i < len(sandboxRules); i++ { + + if rule.SrcIf.Equal(sandboxRules[i].SrcIf) && rule.Whitelist != sandboxRules[i].Whitelist { + return false + } + + } + + return true +} + +func ruleExists(rule sandboxRule) int { + + for i := 0; i < len(sandboxRules); i++ { + + if rule.Whitelist == sandboxRules[i].Whitelist && rule.SrcIf.Equal(sandboxRules[i].SrcIf) && rule.DstIP.Equal(sandboxRules[i].DstIP) && rule.DstPort == sandboxRules[i].DstPort { + return i + } + + } + + return -1 +} + +func ReceiverLoop(c net.Conn) { + defer c.Close() + bio := bufio.NewReader(c) + + for { + buf, err := bio.ReadBytes('\n') + + if err != nil { + log.Notice("Error reading data from IPC client: ", err) + return + } + + data := string(buf) + + if data[len(data)-1] == '\n' { + data = data[0:len(data)-1] + } + + if data == "dump" { + log.Notice("Dumping oz-firewall rule set to client...") + banner := fmt.Sprintf("There are a total of %d rule(s).\n", len(sandboxRules)) + c.Write([]byte(banner)) + + for i := 0; i < len(sandboxRules); i++ { + rulestr := "" + + if sandboxRules[i].Whitelist { + rulestr += "whitelist" + } else { + rulestr += "blacklist" + } + + rulestr += " " + sandboxRules[i].SrcIf.String() + " -> " + sandboxRules[i].DstIP.String() + " : " + strconv.Itoa(int(sandboxRules[i].DstPort)) + "\n" + c.Write([]byte(rulestr)) + } + + return + } else { + tokens := strings.Split(data, " ") + + if len(tokens) != 5 { + log.Notice("IPC received invalid command: " + data) + c.Write([]byte("Received bad number of parameters.\n")) + return + } else if tokens[0] != "add" && tokens[0] != "remove" { + log.Notice("IPC received invalid command: " + data) + c.Write([]byte("Unrecognized command.\n")) + return + } else if tokens[1] != "whitelist" && tokens[1] != "blacklist" { + log.Notice("IPC received invalid command: " + data) + c.Write([]byte("Bad command: must specify either whitelist or blacklist.\n")) + return + } + + add := true + + if tokens[0] == "remove" { + add = false + } + + w := true + + if tokens[1] == "blacklist" { + w = false + } + + srcip := net.ParseIP(tokens[2]) + + if srcip == nil { + log.Notice("IPC received invalid source host: ", tokens[2]) + c.Write([]byte("Bad command: source host address was invalid")) + return + } + + dstip := net.ParseIP(tokens[3]) + + if dstip == nil { + log.Notice("IPC received invalid destination host: ", tokens[3]) + c.Write([]byte("Bad command: dst host address was invalid")) + return + } + + dstport, err := strconv.Atoi(tokens[4]) + + if err != nil || dstport <= 0 || dstport > 65535 { + log.Notice("IPC received invalid destination port: ", tokens[4]) + c.Write([]byte("Bad command: dst port was invalid")) + return + } + + rule := sandboxRule { srcip, dstip, uint16(dstport), w } + + if add && !canAddRule(rule) { + log.Notice("Could not add rule of mismatching type: ", rule) + c.Write([]byte("Error: cannot add rule that would result in mixed whitelist and blacklist")) + return + } + + exists := ruleExists(rule) + + if add && exists != -1 { + log.Notice("IP received request to add existing rule: ", rule) + c.Write([]byte("Error: cannot add already existing rule")) + return + } else if !add && exists == -1 { + log.Notice("IP received request to remove non-existent rule: ", rule) + c.Write([]byte("Error: could not remove non-existent rule")) + return + } + + if add { + log.Notice("Adding new rule to oz sandbox/fw: ", rule) + sandboxRules = append(sandboxRules, rule) + } else { + log.Notice("Removing new rule from oz sandbox/fw: ", rule) + sandboxRules = append(sandboxRules[:exists], sandboxRules[exists+1:]...) + } + + + log.Notice("IPC received command: " + data) + c.Write([]byte("OK.\n")) + return + } + + + } + +} + +func OzReceiver() { + log.Notice("XXX: dispatching oz receiver...") + os.Remove(ReceiverSocketPath) + lfd, err := net.Listen("unix", ReceiverSocketPath) + if err != nil { + log.Fatal("Could not open oz receiver socket:", err) + } + + for { + fd, err := lfd.Accept() + if err != nil { + log.Fatal("Could not accept receiver client:", err) + } + + go ReceiverLoop(fd) + } + +} diff --git a/sgfw/policy.go b/sgfw/policy.go index d00e633..8dca17f 100644 --- a/sgfw/policy.go +++ b/sgfw/policy.go @@ -21,10 +21,22 @@ var _interpreters = []string{ "bash", } +type sandboxRule struct { + SrcIf net.IP + DstIP net.IP + DstPort uint16 + Whitelist bool +} + +var sandboxRules = []sandboxRule { + { net.IP{172,16,1,42}, net.IP{140,211,166,134}, 21, false }, +} + type pendingConnection interface { policy() *Policy procInfo() *procsnitch.Info hostname() string + src() net.IP dst() net.IP dstPort() uint16 accept() @@ -50,14 +62,22 @@ func (pp *pendingPkt) procInfo() *procsnitch.Info { func (pp *pendingPkt) hostname() string { return pp.name } + +func (pp *pendingPkt) src() net.IP { + src, _ := getPacketIP4Addrs(pp.pkt) + return src +} + func (pp *pendingPkt) dst() net.IP { - dst := pp.pkt.Packet.NetworkLayer().NetworkFlow().Dst() + _, dst := getPacketIP4Addrs(pp.pkt) + return dst +/* dst := pp.pkt.Packet.NetworkLayer().NetworkFlow().Dst() if dst.EndpointType() != layers.EndpointIPv4 { return nil } - return dst.Raw() + return dst.Raw() */ // pp.pkt.NetworkLayer().Layer } @@ -79,8 +99,7 @@ func (pp *pendingPkt) accept() { } func (pp *pendingPkt) drop() { -// XXX: This needs to be fixed -// pp.pkt.Mark = 1 + pp.pkt.SetMark(1) pp.pkt.Accept() } @@ -124,20 +143,27 @@ func (fw *Firewall) policyForPath(path string) *Policy { } func (p *Policy) processPacket(pkt *nfqueue.NFQPacket, pinfo *procsnitch.Info) { + +/* hbytes, err := pkt.GetHWAddr() + if err != nil { + log.Notice("Failed to get HW address underlying packet: ", err) + } else { log.Notice("got hwaddr: ", hbytes) } */ p.lock.Lock() defer p.lock.Unlock() dstb := pkt.Packet.NetworkLayer().NetworkFlow().Dst().Raw() dstip := net.IP(dstb) + srcip := net.IP(pkt.Packet.NetworkLayer().NetworkFlow().Src().Raw()) + _, dstp := getPacketPorts(pkt) name := p.fw.dns.Lookup(dstip) -// name := p.fw.dns.Lookup(pkt.Dst) if !FirewallConfig.LogRedact { log.Infof("Lookup(%s): %s", dstip.String(), name) } + fwo := matchAgainstOzRules(srcip, dstip, dstp) +log.Notice("XXX: Attempting to filter packet on rules -> ", fwo) result := p.rules.filterPacket(pkt, pinfo, name) switch result { case FILTER_DENY: -// XXX: this needs to be fixed -// pkt.Mark = 1 + pkt.SetMark(1) pkt.Accept() case FILTER_ALLOW: pkt.Accept() @@ -299,7 +325,24 @@ func (fw *Firewall) filterPacket(pkt *nfqueue.NFQPacket) { } } - _, dstip := getPacketIP4Addrs(pkt) + srcip, dstip := getPacketIP4Addrs(pkt) + _, dstp := getPacketPorts(pkt) + fwo := matchAgainstOzRules(srcip, dstip, dstp) + log.Notice("XXX: Attempting [2] to filter packet on rules -> ", fwo) + + if fwo == OZ_FWRULE_WHITELIST { + log.Noticef("Automatically passed through whitelisted sandbox traffic from %s to %s:%d\n", srcip, dstip, dstp) + pkt.Accept() + return + } else if fwo == OZ_FWRULE_BLACKLIST { + log.Noticef("Automatically blocking blacklisted sandbox traffic from %s to %s:%d\n", srcip, dstip, dstp) + pkt.SetMark(1) + pkt.Accept() + return + } + + + pinfo := findProcessForPacket(pkt) if pinfo == nil { log.Warningf("No proc found for %s", printPacket(pkt, fw.dns.Lookup(dstip), nil)) @@ -390,3 +433,21 @@ func getPacketPorts(pkt *nfqueue.NFQPacket) (uint16, uint16) { return s, d } + +func matchAgainstOzRules(srci, dsti net.IP, dstp uint16) int { + + for i := 0; i < len(sandboxRules); i++ { + + log.Notice("XXX: Attempting to match: ", srci, " / ", dsti, " / ", dstp, " | ", sandboxRules[i]) + + if sandboxRules[i].SrcIf.Equal(srci) && sandboxRules[i].DstIP.Equal(dsti) && sandboxRules[i].DstPort == dstp { + if sandboxRules[i].Whitelist { + return OZ_FWRULE_WHITELIST + } + return OZ_FWRULE_BLACKLIST + } + + } + + return OZ_FWRULE_NONE +} diff --git a/sgfw/prompt.go b/sgfw/prompt.go index 4c71c88..d26f19e 100644 --- a/sgfw/prompt.go +++ b/sgfw/prompt.go @@ -75,7 +75,7 @@ func (p *prompter) processConnection(pc pendingConnection) { addr, int32(pc.dstPort()), pc.dst().String(), - "---", + pc.src().String(), uidToUser(pc.procInfo().UID), int32(pc.procInfo().Pid), FirewallConfig.PromptExpanded, diff --git a/sgfw/sgfw.go b/sgfw/sgfw.go index bc7a90d..1a84724 100644 --- a/sgfw/sgfw.go +++ b/sgfw/sgfw.go @@ -99,6 +99,7 @@ func (fw *Firewall) runFilter() { if err != nil { log.Fatal("Error opening NFQueue:", err) } + q.EnableHWTrace() defer q.Close() go func() { @@ -222,6 +223,8 @@ func Main() { log.Notice("Did not find SOCKS5 configuration file at", defaultSocksCfgPath, "; ignoring subsystem...") } + go OzReceiver() + fw.runFilter() // observe process signals and either diff --git a/sgfw/socks_server_chain.go b/sgfw/socks_server_chain.go index 40ab7c3..8a7172e 100644 --- a/sgfw/socks_server_chain.go +++ b/sgfw/socks_server_chain.go @@ -44,6 +44,7 @@ const ( type pendingSocksConnection struct { pol *Policy hname string + srcIP net.IP destIP net.IP destPort uint16 pinfo *procsnitch.Info @@ -69,6 +70,10 @@ func (sc *pendingSocksConnection) dstPort() uint16 { return sc.destPort } +func (sc *pendingSocksConnection) src() net.IP { + return sc.srcIP +} + func (sc *pendingSocksConnection) deliverVerdict(v int) { sc.verdict <- v close(sc.verdict) @@ -178,7 +183,7 @@ func (c *socksChainSession) addressDetails() (string, net.IP, uint16) { func (c *socksChainSession) filterConnect() bool { pinfo := procsnitch.FindProcessForConnection(c.clientConn, c.procInfo) if pinfo == nil { - log.Warningf("No proc found for connection from: %s", c.clientConn.RemoteAddr()) + log.Warningf("No proc found for [socks5] connection from: %s", c.clientConn.RemoteAddr()) return false } @@ -199,6 +204,7 @@ func (c *socksChainSession) filterConnect() bool { pol: policy, hname: hostname, destIP: ip, + srcIP: net.IP{0,0,0,0}, destPort: port, pinfo: pinfo, verdict: make(chan int),