blob: 3df3c5e7022b8803ff77b242c130652c757e604a [file] [log] [blame]
From e4839f64c6608a2199d46819e77ed03233563301 Mon Sep 17 00:00:00 2001
From: Alban Crequy <alban.crequy@collabora.co.uk>
Date: Fri, 22 Jun 2012 14:20:50 +0200
Subject: [PATCH 15/15] netfilter: add netfilter D-Bus module
AF_BUS has netfilter hooks on the packet sending path. This allows the
netfilter subsystem to register netfilter hook handlers.
The netfilter_dbus module allows to inspect D-Bus messages and take
actions based on the information contained on these messages.
Signed-off-by: Alban Crequy <alban.crequy@collabora.co.uk>
---
net/bus/Kconfig | 2 +
net/bus/Makefile | 3 +
net/bus/nfdbus/Kconfig | 12 ++
net/bus/nfdbus/Makefile | 6 +
net/bus/nfdbus/nfdbus.c | 458 +++++++++++++++++++++++++++++++++++++++++++++++
net/bus/nfdbus/nfdbus.h | 44 +++++
6 files changed, 525 insertions(+), 0 deletions(-)
create mode 100644 net/bus/nfdbus/Kconfig
create mode 100644 net/bus/nfdbus/Makefile
create mode 100644 net/bus/nfdbus/nfdbus.c
create mode 100644 net/bus/nfdbus/nfdbus.h
diff --git a/net/bus/Kconfig b/net/bus/Kconfig
index 5f01410..4ec0beec 100644
--- a/net/bus/Kconfig
+++ b/net/bus/Kconfig
@@ -13,3 +13,5 @@ config AF_BUS
called bus.
Say N unless you know what you are doing.
+
+source "net/bus/nfdbus/Kconfig"
diff --git a/net/bus/Makefile b/net/bus/Makefile
index 8c1fea2..a6ba263 100644
--- a/net/bus/Makefile
+++ b/net/bus/Makefile
@@ -5,3 +5,6 @@
obj-$(CONFIG_AF_BUS) += af-bus.o
af-bus-y := af_bus.o garbage.o
+
+# Dbus
+obj-$(CONFIG_NETFILTER_DBUS) += nfdbus/
diff --git a/net/bus/nfdbus/Kconfig b/net/bus/nfdbus/Kconfig
new file mode 100644
index 0000000..25699a1
--- /dev/null
+++ b/net/bus/nfdbus/Kconfig
@@ -0,0 +1,12 @@
+#
+# Netfilter D-Bus module configuration
+#
+config NETFILTER_DBUS
+ tristate "Netfilter D-bus (EXPERIMENTAL)"
+ depends on AF_BUS && CONNECTOR && EXPERIMENTAL
+ ---help---
+ If you say Y here, you will include support for a netfilter hook to
+ parse D-Bus messages sent using the AF_BUS socket address family.
+
+ To compile this as a module, choose M here: the module will be
+ called netfilter_dbus.
diff --git a/net/bus/nfdbus/Makefile b/net/bus/nfdbus/Makefile
new file mode 100644
index 0000000..1a825f8
--- /dev/null
+++ b/net/bus/nfdbus/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the netfilter D-Bus module
+#
+obj-$(CONFIG_NETFILTER_DBUS) += netfilter_dbus.o
+
+netfilter_dbus-y := nfdbus.o message.o matchrule.o
diff --git a/net/bus/nfdbus/nfdbus.c b/net/bus/nfdbus/nfdbus.c
new file mode 100644
index 0000000..be8387a
--- /dev/null
+++ b/net/bus/nfdbus/nfdbus.c
@@ -0,0 +1,458 @@
+/*
+ * nfdbus.c - Netfilter module for AF_BUS/BUS_PROTO_DBUS.
+ */
+
+#define DRIVER_AUTHOR "Alban Crequy"
+#define DRIVER_DESC "Netfilter module for AF_BUS/BUS_PROTO_DBUS."
+
+#include "nfdbus.h"
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter.h>
+#include <linux/connector.h>
+#include <net/af_bus.h>
+
+#include "message.h"
+#include "matchrule.h"
+
+static struct nf_hook_ops nfho_dbus;
+
+static struct cb_id cn_cmd_id = { CN_IDX_NFDBUS, CN_VAL_NFDBUS };
+
+static unsigned int hash;
+
+/* Scoped by AF_BUS address */
+struct hlist_head matchrules_table[BUS_HASH_SIZE];
+DEFINE_SPINLOCK(matchrules_lock);
+
+static struct bus_match_maker *find_match_maker(struct sockaddr_bus *addr,
+ bool create, bool delete)
+{
+ u64 hash;
+ struct hlist_node *node;
+ struct bus_match_maker *matchmaker;
+ int path_len = strlen(addr->sbus_path);
+
+ hash = csum_partial(addr->sbus_path,
+ strlen(addr->sbus_path), 0);
+ hash ^= addr->sbus_addr.s_addr;
+ hash ^= hash >> 32;
+ hash ^= hash >> 16;
+ hash ^= hash >> 8;
+ hash &= 0xff;
+
+ spin_lock(&matchrules_lock);
+ hlist_for_each_entry(matchmaker, node, &matchrules_table[hash],
+ table_node) {
+ if (addr->sbus_family == matchmaker->addr.sbus_family &&
+ addr->sbus_addr.s_addr == matchmaker->addr.sbus_addr.s_addr &&
+ !memcmp(addr->sbus_path, matchmaker->addr.sbus_path,
+ path_len)) {
+ kref_get(&matchmaker->kref);
+ if (delete)
+ hlist_del(&matchmaker->table_node);
+ spin_unlock(&matchrules_lock);
+ pr_debug("Found matchmaker for hash %llu", hash);
+ return matchmaker;
+ }
+ }
+ spin_unlock(&matchrules_lock);
+
+ if (!create) {
+ pr_debug("Matchmaker for hash %llu not found", hash);
+ return NULL;
+ }
+
+ matchmaker = bus_matchmaker_new(GFP_ATOMIC);
+ matchmaker->addr.sbus_family = addr->sbus_family;
+ matchmaker->addr.sbus_addr.s_addr = addr->sbus_addr.s_addr;
+ memcpy(matchmaker->addr.sbus_path, addr->sbus_path, BUS_PATH_MAX);
+
+ pr_debug("Create new matchmaker for hash %llu\n", hash);
+ spin_lock(&matchrules_lock);
+ hlist_add_head(&matchmaker->table_node, &matchrules_table[hash]);
+ kref_get(&matchmaker->kref);
+ spin_unlock(&matchrules_lock);
+ return matchmaker;
+}
+
+static unsigned int dbus_filter(unsigned int hooknum,
+ struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct bus_send_context *sendctx;
+ struct bus_match_maker *matchmaker = NULL;
+ struct bus_match_maker *sender = NULL;
+ struct dbus_message msg = {0,};
+ unsigned char *data;
+ size_t len;
+ int err;
+ int ret;
+
+ if (!skb->sk || skb->sk->sk_family != PF_BUS) {
+ WARN(1, "netfilter_dbus received an invalid skb");
+ return NF_DROP;
+ }
+
+ data = skb->data;
+ sendctx = BUSCB(skb).sendctx;
+ if (!sendctx || !sendctx->sender || !sendctx->sender_socket) {
+ WARN(1, "netfilter_dbus received an AF_BUS packet" \
+ " without context. This is a bug. Dropping the"
+ " packet.");
+ return NF_DROP;
+ }
+
+ if (sendctx->sender_socket->sk->sk_protocol != BUS_PROTO_DBUS) {
+ /* This kernel module is for D-Bus. It must not
+ * interfere with other users of AF_BUS. */
+ return NF_ACCEPT;
+ }
+ if (sendctx->recipient)
+ matchmaker = find_match_maker(sendctx->recipient, false, false);
+
+ len = skb_tail_pointer(skb) - data;
+
+ if (sendctx->to_master && sendctx->main_recipient) {
+ pr_debug("AF_BUS packet to the bus master. ACCEPT.\n");
+ ret = NF_ACCEPT;
+ goto out;
+ }
+
+ if (sendctx->main_recipient && !sendctx->bus_master_side) {
+ pr_debug("AF_BUS packet from a peer to a peer (unicast). ACCEPT.\n");
+ ret = NF_ACCEPT;
+ goto out;
+ }
+
+ err = dbus_message_parse(data, len, &msg);
+ if (err) {
+ if (!sendctx->main_recipient) {
+ pr_debug("AF_BUS packet for an eavesdropper or " \
+ "multicast is not parsable. DROP.\n");
+ ret = NF_DROP;
+ goto out;
+ } else if (sendctx->bus_master_side) {
+ pr_debug("AF_BUS packet from bus master is not parsable. ACCEPT.\n");
+ ret = NF_ACCEPT;
+ goto out;
+ } else {
+ pr_debug("AF_BUS packet from peer is not parsable. DROP.\n");
+ ret = NF_DROP;
+ goto out;
+ }
+ }
+
+ if (sendctx->bus_master_side && !sendctx->main_recipient) {
+ pr_debug("AF_BUS packet '%s' from the bus master is for an " \
+ "eavesdropper. DROP.\n",
+ msg.member ? msg.member : "");
+ ret = NF_DROP;
+ goto out;
+ }
+ if (sendctx->bus_master_side) {
+ if (msg.name_acquired) {
+ pr_debug("New name: %s [%p %p].\n",
+ msg.name_acquired, sendctx->sender,
+ sendctx->recipient);
+
+ sender = find_match_maker(sendctx->sender, true, false);
+ bus_matchmaker_add_name(sender, msg.name_acquired,
+ GFP_ATOMIC);
+ }
+ if (msg.name_lost) {
+ pr_debug("Lost name: %s [%p %p].\n",
+ msg.name_lost, sendctx->sender,
+ sendctx->recipient);
+
+ sender = find_match_maker(sendctx->sender, true, false);
+ bus_matchmaker_remove_name(sender, msg.name_acquired);
+ }
+
+ pr_debug("AF_BUS packet '%s' from the bus master. ACCEPT.\n",
+ msg.member ? msg.member : "");
+ ret = NF_ACCEPT;
+ goto out;
+ }
+
+ pr_debug("Multicast AF_BUS packet, %ld bytes, " \
+ "considering recipient %lld...\n", len,
+ sendctx->recipient ? sendctx->recipient->sbus_addr.s_addr : 0);
+
+ pr_debug("Message type %d %s->%s [iface: %s][member: %s][matchmaker=%p]...\n",
+ msg.type,
+ msg.sender ? msg.sender : "",
+ msg.destination ? msg.destination : "",
+ msg.interface ? msg.interface : "",
+ msg.member ? msg.member : "",
+ matchmaker);
+
+ if (!matchmaker) {
+ pr_debug("No match rules for this recipient. DROP.\n");
+ ret = NF_DROP;
+ goto out;
+ }
+
+ sender = find_match_maker(sendctx->sender, true, false);
+ err = bus_matchmaker_filter(matchmaker, sender, sendctx->eavesdropper,
+ &msg);
+ if (err) {
+ pr_debug("Matchmaker: ACCEPT.\n");
+ ret = NF_ACCEPT;
+ goto out;
+ } else {
+ pr_debug("Matchmaker: DROP.\n");
+ ret = NF_DROP;
+ goto out;
+ }
+
+out:
+ if (matchmaker)
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ if (sender)
+ kref_put(&sender->kref, bus_matchmaker_free);
+ return ret;
+}
+
+/* Taken from drbd_nl_send_reply() */
+static void nfdbus_nl_send_reply(struct cn_msg *msg, int ret_code)
+{
+ char buffer[sizeof(struct cn_msg)+sizeof(struct nfdbus_nl_cfg_reply)];
+ struct cn_msg *cn_reply = (struct cn_msg *) buffer;
+ struct nfdbus_nl_cfg_reply *reply =
+ (struct nfdbus_nl_cfg_reply *)cn_reply->data;
+ int rr;
+
+ memset(buffer, 0, sizeof(buffer));
+ cn_reply->id = msg->id;
+
+ cn_reply->seq = msg->seq;
+ cn_reply->ack = msg->ack + 1;
+ cn_reply->len = sizeof(struct nfdbus_nl_cfg_reply);
+ cn_reply->flags = 0;
+
+ reply->ret_code = ret_code;
+
+ rr = cn_netlink_send(cn_reply, 0, GFP_NOIO);
+ if (rr && rr != -ESRCH)
+ pr_debug("nfdbus: cn_netlink_send()=%d\n", rr);
+}
+
+/**
+ * nfdbus_check_perm - check if a pid is allowed to update match rules
+ * @sockaddr_bus: the socket address of the bus
+ * @pid: the process id that wants to update the match rules set
+ *
+ * Test if a given process id is allowed to update the match rules set
+ * for this bus. Only the process that owns the bus master listen socket
+ * is allowed to update the match rules set for the bus.
+ */
+static bool nfdbus_check_perm(struct sockaddr_bus *sbusname, pid_t pid)
+{
+ struct net *net = get_net_ns_by_pid(pid);
+ struct sock *s;
+ struct bus_address *addr;
+ struct hlist_node *node;
+ int offset = (sbusname->sbus_path[0] == '\0');
+ int path_len = strnlen(sbusname->sbus_path + offset, BUS_PATH_MAX);
+ int len;
+ if (!net)
+ return false;
+
+ len = path_len + 1 + sizeof(__kernel_sa_family_t) +
+ sizeof(struct bus_addr);
+
+ spin_lock(&bus_address_lock);
+
+ hlist_for_each_entry(addr, node, &bus_address_table[hash],
+ table_node) {
+ s = addr->sock;
+
+ if (s->sk_protocol != BUS_PROTO_DBUS)
+ continue;
+
+ if (!net_eq(sock_net(s), net))
+ continue;
+
+ if (addr->len == len &&
+ addr->name->sbus_family == sbusname->sbus_family &&
+ addr->name->sbus_addr.s_addr == BUS_MASTER_ADDR &&
+ bus_same_bus(addr->name, sbusname) &&
+ pid_nr(s->sk_peer_pid) == pid) {
+ spin_unlock(&bus_address_lock);
+ return true;
+ }
+ }
+
+ spin_unlock(&bus_address_lock);
+
+ return false;
+}
+
+static void cn_cmd_cb(struct cn_msg *msg, struct netlink_skb_parms *nsp)
+{
+ struct nfdbus_nl_cfg_req *nlp = (struct nfdbus_nl_cfg_req *)msg->data;
+ struct cn_msg *cn_reply;
+ struct nfdbus_nl_cfg_reply *reply;
+ int retcode, rr;
+ pid_t pid = task_tgid_vnr(current);
+ int reply_size = sizeof(struct cn_msg)
+ + sizeof(struct nfdbus_nl_cfg_reply);
+
+ pr_debug("nfdbus: %s nsp->pid=%d pid=%d\n", __func__, nsp->pid, pid);
+
+ if (!nfdbus_check_perm(&nlp->addr, pid)) {
+ pr_debug(KERN_ERR "nfdbus: pid=%d is not allowed!\n", pid);
+ retcode = EPERM;
+ goto fail;
+ }
+
+ cn_reply = kzalloc(reply_size, GFP_KERNEL);
+ if (!cn_reply) {
+ retcode = ENOMEM;
+ goto fail;
+ }
+ reply = (struct nfdbus_nl_cfg_reply *) cn_reply->data;
+
+ if (msg->len < sizeof(struct nfdbus_nl_cfg_req)) {
+ reply->ret_code = EINVAL;
+ } else if (nlp->cmd == NFDBUS_CMD_ADDMATCH) {
+ struct bus_match_rule *rule;
+ struct bus_match_maker *matchmaker;
+ reply->ret_code = 0;
+
+ if (msg->len == 0)
+ reply->ret_code = EINVAL;
+
+ rule = bus_match_rule_parse(nlp->data, GFP_ATOMIC);
+ if (rule) {
+ matchmaker = find_match_maker(&nlp->addr, true, false);
+ pr_debug("Add match rule for matchmaker %p\n",
+ matchmaker);
+ bus_matchmaker_add_rule(matchmaker, rule);
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ } else {
+ reply->ret_code = EINVAL;
+ }
+ } else if (nlp->cmd == NFDBUS_CMD_REMOVEMATCH) {
+ struct bus_match_rule *rule;
+ struct bus_match_maker *matchmaker;
+
+ rule = bus_match_rule_parse(nlp->data, GFP_ATOMIC);
+ if (rule) {
+ matchmaker = find_match_maker(&nlp->addr, false, false);
+ if (!matchmaker) {
+ reply->ret_code = EINVAL;
+ } else {
+ pr_debug("Remove match rule for matchmaker %p\n",
+ matchmaker);
+ bus_matchmaker_remove_rule_by_value(matchmaker, rule);
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ reply->ret_code = 0;
+ }
+ bus_match_rule_free(rule);
+ }
+
+ } else if (nlp->cmd == NFDBUS_CMD_REMOVEALLMATCH) {
+ struct bus_match_maker *matchmaker;
+
+ matchmaker = find_match_maker(&nlp->addr, false, true);
+ if (!matchmaker) {
+ reply->ret_code = EINVAL;
+ } else {
+ pr_debug("Remove matchmaker %p\n", matchmaker);
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ reply->ret_code = 0;
+ }
+
+ } else {
+ reply->ret_code = EINVAL;
+ }
+
+ cn_reply->id = msg->id;
+ cn_reply->seq = msg->seq;
+ cn_reply->ack = msg->ack + 1;
+ cn_reply->len = sizeof(struct nfdbus_nl_cfg_reply);
+ cn_reply->flags = 0;
+
+ rr = cn_netlink_reply(cn_reply, nsp->pid, GFP_KERNEL);
+ if (rr && rr != -ESRCH)
+ pr_debug("nfdbus: cn_netlink_send()=%d\n", rr);
+ pr_debug("nfdbus: cn_netlink_reply(pid=%d)=%d\n", nsp->pid, rr);
+
+ kfree(cn_reply);
+ return;
+fail:
+ nfdbus_nl_send_reply(msg, retcode);
+}
+
+static int __init nfdbus_init(void)
+{
+ int err;
+ struct bus_addr master_addr;
+
+ master_addr.s_addr = BUS_MASTER_ADDR;
+ hash = bus_compute_hash(master_addr);
+
+ pr_debug("Loading netfilter_dbus\n");
+
+ /* Install D-Bus netfilter hook */
+ nfho_dbus.hook = dbus_filter;
+ nfho_dbus.hooknum = NF_BUS_SENDING;
+ nfho_dbus.pf = NFPROTO_BUS; /* Do not use PF_BUS, you fool! */
+ nfho_dbus.priority = 0;
+ nfho_dbus.owner = THIS_MODULE;
+ err = nf_register_hook(&nfho_dbus);
+ if (err)
+ return err;
+ pr_debug("Netfilter hook for D-Bus: installed.\n");
+
+ /* Install connector hook */
+ err = cn_add_callback(&cn_cmd_id, "nfdbus", cn_cmd_cb);
+ if (err)
+ goto err_cn_cmd_out;
+ pr_debug("Connector hook: installed.\n");
+
+ return 0;
+
+err_cn_cmd_out:
+ nf_unregister_hook(&nfho_dbus);
+
+ return err;
+}
+
+static void __exit nfdbus_cleanup(void)
+{
+ int i;
+ struct hlist_node *node, *tmp;
+ struct bus_match_maker *matchmaker;
+ nf_unregister_hook(&nfho_dbus);
+
+ cn_del_callback(&cn_cmd_id);
+
+ spin_lock(&matchrules_lock);
+ for (i = 0; i < BUS_HASH_SIZE; i++) {
+ hlist_for_each_entry_safe(matchmaker, node, tmp,
+ &matchrules_table[i], table_node) {
+ hlist_del(&matchmaker->table_node);
+ kref_put(&matchmaker->kref, bus_matchmaker_free);
+ }
+ }
+ spin_unlock(&matchrules_lock);
+
+ pr_debug("Unloading netfilter_dbus\n");
+}
+
+module_init(nfdbus_init);
+module_exit(nfdbus_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_ALIAS_NET_PF_PROTO(PF_BUS, BUS_PROTO_DBUS);
diff --git a/net/bus/nfdbus/nfdbus.h b/net/bus/nfdbus/nfdbus.h
new file mode 100644
index 0000000..477bde3
--- /dev/null
+++ b/net/bus/nfdbus/nfdbus.h
@@ -0,0 +1,44 @@
+/*
+ * nfdbus.h Netfilter module for AF_BUS/BUS_PROTO_DBUS.
+ *
+ * Copyright (C) 2012 Collabora Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef NETFILTER_DBUS_H
+#define NETFILTER_DBUS_H
+
+#include <linux/types.h>
+#include <linux/bus.h>
+
+#define NFDBUS_CMD_ADDMATCH 0x01
+#define NFDBUS_CMD_REMOVEMATCH 0x02
+#define NFDBUS_CMD_REMOVEALLMATCH 0x03
+
+struct nfdbus_nl_cfg_req {
+ __u32 cmd;
+ __u32 len;
+ struct sockaddr_bus addr;
+ __u64 pad;
+ unsigned char data[0];
+};
+
+struct nfdbus_nl_cfg_reply {
+ __u32 ret_code;
+};
+
+#endif /* NETFILTER_DBUS_H */
--
1.7.7.6