diff options
author | Ben Brewer <benbrewer@codethink.co.uk> | 2012-11-19 17:54:24 +0000 |
---|---|---|
committer | Ben Brewer <benbrewer@codethink.co.uk> | 2012-11-19 17:54:24 +0000 |
commit | da53d55583196c05e470c3dd8e24b24e22c3b741 (patch) | |
tree | edafcd2f7858a668bb9c07fcee46cc2341fa4988 | |
parent | 5edf3233eb6a511a33d15dde7a97a069e8a47498 (diff) | |
download | linux-da53d55583196c05e470c3dd8e24b24e22c3b741.tar.gz |
Applied AF_DBUS patch to kernelbaserock/genivi/af_dbus
-rw-r--r-- | Documentation/networking/af_bus.txt | 558 | ||||
-rw-r--r-- | drivers/connector/connector.c | 32 | ||||
-rw-r--r-- | include/linux/bus.h | 34 | ||||
-rw-r--r-- | include/linux/connector.h | 1 | ||||
-rw-r--r-- | include/linux/security.h | 11 | ||||
-rw-r--r-- | include/linux/socket.h | 5 | ||||
-rw-r--r-- | include/net/af_bus.h | 273 | ||||
-rw-r--r-- | net/Kconfig | 1 | ||||
-rw-r--r-- | net/Makefile | 1 | ||||
-rw-r--r-- | net/bus/Kconfig | 17 | ||||
-rw-r--r-- | net/bus/Makefile | 10 | ||||
-rw-r--r-- | net/bus/af_bus.c | 2688 | ||||
-rw-r--r-- | net/bus/garbage.c | 322 | ||||
-rw-r--r-- | net/bus/nfdbus/Kconfig | 12 | ||||
-rw-r--r-- | net/bus/nfdbus/Makefile | 6 | ||||
-rw-r--r-- | net/bus/nfdbus/matchrule.c | 1132 | ||||
-rw-r--r-- | net/bus/nfdbus/matchrule.h | 82 | ||||
-rw-r--r-- | net/bus/nfdbus/message.c | 194 | ||||
-rw-r--r-- | net/bus/nfdbus/message.h | 71 | ||||
-rw-r--r-- | net/bus/nfdbus/nfdbus.c | 456 | ||||
-rw-r--r-- | net/bus/nfdbus/nfdbus.h | 44 | ||||
-rw-r--r-- | net/core/scm.c | 3 | ||||
-rw-r--r-- | net/core/sock.c | 6 | ||||
-rw-r--r-- | security/capability.c | 7 | ||||
-rw-r--r-- | security/security.c | 7 | ||||
-rw-r--r-- | security/selinux/hooks.c | 35 |
26 files changed, 6003 insertions, 5 deletions
diff --git a/Documentation/networking/af_bus.txt b/Documentation/networking/af_bus.txt new file mode 100644 index 000000000000..a0b078f9fe3f --- /dev/null +++ b/Documentation/networking/af_bus.txt @@ -0,0 +1,558 @@ + The AF_BUS socket address family + ================================ + +Introduction +------------ + +AF_BUS is a message oriented inter process communication system. + +The principle features are: + + - Reliable datagram based communication (all sockets are of type + SOCK_SEQPACKET) + + - Multicast message delivery (one to many, unicast as a subset) + + - Strict ordering (messages are delivered to every client in the same order) + + - Ability to pass file descriptors + + - Ability to pass credentials + +The basic concept is to provide a virtual bus on which multiple +processes can communicate and policy is imposed by a "bus master". + +A process can create buses to which other processes can connect and +communicate with each other by sending messages. Processes' addresses +are automatically assigned by the bus on connect and are +unique. Messages can be sent either to a process' unique address or to +a bus multicast addresses. + +Netfilter rules or Berkeley Packet Filter can be used to restrict the +messages that each peer is allowed to receive. This is especially +important when sending to multicast addresses. + +Besides messages, process can send and receive ancillary data (i.e., +SCM_RIGHTS for passing file descriptors or SCM_CREDENTIALS for passing +Unix credentials). In the case of a multicast message all recipients +of a message may obtain a copy a file descriptor or credentials. + +A bus is created by processes connecting on an AF_BUS socket. The +"bus master" binds itself instead of connecting to the NULL address. + +The socket address is made up of a path component and a numeric +component. The path component is either a pathname or an abstract +socket similar to a unix socket. The numeric component is used to +uniquely identify each connection to the bus. Thus the path identifies +a specific bus and the numeric component the attachment to that bus. + +The process that calls bind(2) on the socket is the owner of the bus +and is called the bus master. The master is a special client of the +bus and has some responsibility for the bus' operation. The master is +assigned a fixed address with all the bits zero (0x0000000000000000). + +Each process connected to an AF_BUS socket has one or more addresses +within that bus. These addresses are 64-bit unsigned integers, +interpreted by splitting the address into two parts: the most +significant 16 bits are a prefix identifying the type of address, and +the remaining 48 bits are the actual client address within that +prefix, as shown in this figure: + +Bit: 0 15 16 63 + +----------------+------------------------------------------------+ + | Type prefix | Client address | + +----------------+------------------------------------------------+ + +The prefix with all bits zero is reserved for use by the kernel, which +automatically assigns one address from this prefix to each client on +connection. The address in this prefix with all bits zero is always +assigned to the bus master. Addresses on the prefix 0x0000 are unique +and will never repeat for the lifetime of the bus master. + +A client may have multiple addresses. When data is sent to other +clients, those clients will always see the sender address that is in +the prefix 0x0000 address space when calling recvmsg(2) or +recvfrom(2). Similarly, the prefix 0x0000 address is returned by calls +to getsockname(2) and getpeername(2). + +For each prefix, the address where the least significant 48 bits are +all 1 (i.e., 0xffffffffffff) is also reserved, and can be used to send +multicast messages to all the peers on a prefix. + +The non-reserved addresses in each of the remaining prefixes are +managed by the bus master, which may assign additional addresses to +any other connected socket. + +Having different name-spaces has two advantages: + + - Clients can have addresses on different mutually-exclusive + scopes. This permits sending multicast packets to only clients + that have addresses on a given prefix. + + - The addressing scheme can be more flexible. The kernel will only + assign unique addresses on the all-bits-zero prefix (0x0000) and + allows the bus master process to assign additional addresses to + clients on other prefixes. By having different prefixes, the + kernel and bus master assignments will not collide. + +AF_BUS transport can support two network topologies. When a process +first connects to the bus master, it can only communicate with the bus +master. The process can't send and receive packets from other peers on +the bus. So, from the client process point of view the network +topology is point-to-point. + +The bus master can allow the connected peer to be part of the bus and +start to communicate with other peers by setting a socket option with +the setsockopt(2) system call using the accepted socket descriptor. At +this point, the topology becomes a bus to the client process. + +Packets whose destination address is not assigned to any client are +routed by default to the bus master (the client accepted socket +descriptor). + + +Semantics +--------- + +Bus features: + + - Unicast and multicast addressing scheme. + - Ability to assign addresses from user-space with different prefixes. + - Automatic address assignment. + - Ordered packets delivery (FIFO, total ordering). + - File descriptor and credentials passing. + - Support for both point-to-point and bus network topologies. + - Bus control access managed from user-space. + - Netfilter hooks for packet sending, routing and receiving. + +A process (the "bus master") can create an AF_BUS bus with socket(2) +and use bind(2) to assign an address to the bus. Then it can listen(2) +on the created socket to start accepting incoming connections with +accept(2). + +Processes can connect to the bus by creating a socket with socket(2) +and using connect(2). The kernel will assign a unique address to each +connection and messages can be sent and received by using BSD socket +primitives. + +This uses the connect(2) semantic in a non-traditional way, with +AF_BUS sockets, it's not possible to connect "my" socket to a specific +peer socket whereas the traditional BSD sockets API usage, connect(2) +either connects to stream sockets, or assigns a peer address to a +datagram socket (so that send(2) can be used instead of sendto()). + +An AF_BUS socket address is represented as a combination of a bus +address and a bus path name. Address are unique within a path. The +unique bus address is further subdivided into a prefix and a client +address. Thus the path identifies a specific bus and the numeric +component the attachment to that bus. + +#define BUS_PATH_MAX 108 + +/* Bus address */ +struct bus_addr { + uint64_t s_addr; /* 16-bit prefix + 48-bit client address */ +}; + +/* Structure describing an AF_BUS socket address. */ +struct sockaddr_bus { + sa_family_t sbus_family; /* AF_BUS */ + struct bus_addr sbus_addr; /* bus address */ + char sbus_path[BUS_PATH_MAX]; /* pathname */ +}; + +A process becomes a bus master for a given struct sockaddr_bus by +calling bind(2) on an AF_BUS addresses. The argument must be { AF_BUS, +0, path }. + +AF_BUS supports both abstract and non-abstract path names. Abstract +names are distinguished by the fact that sbus_path[0] == '\0' and they +don't represent file system paths while non-abstract paths are bound +to a file system path name. (See the unix(7) man page for a discussion +of abstract socket addresses in the AF_UNIX address family.) + +Then the process calls listen(2) to accept incoming connections. If +that process calls getsockname(2), the returned address will be { +AF_BUS, 0, path }. + +The conventional string form of the full address is path + ":" + +prefix + "/" + client address. Prefix and client address are +represented in hex. + +For example the address: + +struct sockaddr_bus addr; +addr.sbus_family = AF_BUS; +strcpy(addr.sbus_path, "/tmp/test"); +addr.sbus_addr.s_addr = 0x0002f00ddeadbeef; + +would be represented using the string /tmp/test:0002/f00ddeadbeef. + +If the bus_addr is 0, then both the prefix and client address may be +omitted from the string form. To connect to a bus as a client it is +sufficient to specify the path, since the listening address always has +bus_addr == 0. it is not meanigful to specify 'bus_addr' as other than +0 on connect() + +The AF_BUS implementation will automatically assign a unique address +to each client but the bus master can assign additional addresses on a +different prefix by means of the setsockopt(2) system call. For +example: + +struct bus_addr addr; +addr.s_addr = 0x0001deadfee1dead; +ret = setsockopt(afd, SOL_BUS, BUS_ADD_ADDR, &addr, sizeof(addr)); + +where afd is the accepted socket descriptor in the daemon. To show graphically: + + L The AF_BUS listening socket } + / | \ }-- listener process + A1 A2 A3 The AF_BUS accepted sockets } + | | | + C1 C2 C3 The AF_BUS connected sockets }-- client processes + +So if setsockopt(A1, SOL_BUS, BUS_ADD_ADDR, &addr, sizeof(addr)) is +called, C1 will get the new address. + +The inverse operation is BUS_DEL_ADDR, which the bus master can use to +remove a client socket AF_BUS address: + +ret = setsockopt(afd, SOL_BUS, BUS_DEL_ADDR, &addr, sizeof(addr)); + +Besides assigning additional addresses, the bus master has to allow a +client process to communicate with other peers on the bus using a +setsockopt(2): + +ret = setsockopt(afd, SOL_BUS, BUS_JOIN_BUS, NULL, 0); + +Clients are not meant to send messages to each other until the master +tells them (in a protocol-specific way) that the BUS_JOIN_BUS +setsockopt(2) call was made. + +If a client sends a message to a destination other than the bus +master's all-zero address before joining the bus, a EHOSTUNREACH (No +route to host) error is returned since the only host that exists in +the point-to-point network before the client joins the bus are the +client and the bus master. + +A EHOSTUNREACH is returned if a client that joined a bus tries to send +a packet to a client from another bus. Cross-bus communication is not +permited. + +When a process wants to send a unicast message to a peer, it fills a +sockaddr structure and performs a socket operation (i.e., sendto(2)) + +struct sockaddr_bus addr; +char *msg = "Hello world"; + +addr.sbus_family = AF_BUS; +strcpy(addr.sbus_path, "/tmp/test"); +addr.sbus_addr.s_addr = 0x0001f00ddeadbeef; + +ret = sendto(sockfd, "Hello world", strlen("Hello world"), 0, + (struct sockaddr*)&addr, sizeof(addr)); + +The current implementation requires that the addr.sbus_path component +match the one used to conenct() to the bus but in future this +requirement will be removed. + +The kernel will first check that the socket is connected and that the +bus path of the socket correspond with the destination, then it will +extract the prefix and client address from the bus address using a +fixed 16 -bit bitmask. + +prefix = bus address >> 48 & 0xffff +client address = bus address & 0xffff + +If the client address is not all bits one, then the message is unicast +and is delivered to the socket with that assigned address +(0x0001f00ddeadbeef). Otherwise the message is multicast and is +delivered to all the peers with this address prefix (0x0001 in this +case). + +So, when a process wants to send a multicast message, it just has to +fill the address structure with the address prefix + 0xffffffffffff: + +struct sockaddr_bus addr; +char *msg = "Hello world"; + +addr.bus_family = AF_BUS; +strcpy(addr.sbus_path, "/tmp/test"); +addr.bus_addr = 0x0001ffffffffffff; + +ret = sendto(sockfd, "Hello world", strlen("Hello world"), 0, + (struct sockaddr*)&addr, sizeof(addr)); + +The kernel, will apply the binary and operation, learn that the +address is 0xffffffffffff and send the message to all the peers on +this prefix (0x0001). + +Socket transmit queued bytes are limited by a maximum send buffer size +(sysctl_wmem_max) defined in the kernel and can be modified at runtime +using the sysctl interface on /proc/sys/net/core/wmem_default. This +parameter is global for all the sockets families in a Linux system. + +AF_BUS permits the definition of a per-bus maximum send buffer size +using the BUS_SET_SENDBUF socket option. The bus master can call the +setsockopt(2) system call using as a parameter the listening socket. +The command sets a maximum write buffer that will be imposed on each +new socket that connects to the bus: + +ret = setsockopt(serverfd, SOL_BUS, BUS_SET_SENDBUF, &sndbuf, +sizeof(int)); + +In the transmission path both Berkeley Packet Filters and Netfilter +hooks are available, so they can be used to filter sending packets. + + +Using this addressing scheme with D-Bus +--------------------------------------- + +As an example of a use case for AF_BUS, let's analyze how the D-Bus +IPC system can be implemented on top of it. + +We define a new D-Bus address type "afbus". + +A D-Bus client may connect to an address of the form "afbus:path=X" +where X is a string. This means that it connect()s to { AF_BUS, 0, X }. + +For example: afbus:path=/tmp/test connects to { AF_BUS, 0, /tmp/test }. + +A D-Bus daemon may listen on the address "afbus:", which means that it +binds to { AF_BUS, 0, /tmp/test }. It will advertise an address of the +form "afbus:path=/tmp/test" to clients, for instance via the +--print-address option, or via dbus-launch setting the +DBUS_SESSION_BUS_ADDRESS environment variable. For instance, "afbus:" +is an appropriate default listening address for the session bus, +resulting in dbus-launch setting the DBUS_SESSION_BUS_ADDRESS +environment variable to something like +"afbus:path=/tmp/test,guid=...". + +A D-Bus daemon may listen on the address "afbus:file=/some/file", +which means that it will do as above, then write its path into the +given well-known file. For instance, +"afbus:file=/run/dbus/system_bus.afbus" is an appropriate listening +address for the system bus. Only processes with suitable privileges to +write to that file can impersonate the system bus. + +D-Bus clients wishing to connect to the well-known system bus should +attempt to connect to afbus:file=/run/dbus/system_bus.afbus, falling +back to unix:path=/var/run/dbus/system_bus_socket if that fails. On +Linux systems, the well-known system bus daemon should attempt to +listen on both of those addresses. + +The D-Bus daemon will serve as bus master as well since it will be the +process that creates and listens on the AF_BUS socket. + +D-Bus clients will use the fixed bus master address (all zero bits) to +send messages to the D-Bus daemon and the client's unique address to +send messages to other D-Bus clients using the bus. + +When initially connected, D-Bus clients will only be able to +communicate with the D-Bus daemon and will send authentication +information (AUTH message and SCM_CREDENTIALS ancillary +messages). Since the D-Bus daemon is also the bus master, it can allow +D-Bus clients to join the bus and be able to send and receive D-Bus +messages from other peers. + +On connection, the kernel will assign to each client an address in the +prefix 0x0000. If a client attempts to send messages to clients other +than the bus master, this is considered to be an error, and is +prevented by the kernel. + +When the D-Bus daemon has authenticated a client and determined that +it is authorized to be on this bus, it uses a setsockopt(2) call to +tell the kernel that this client has permission to send messages. The +D-Bus daemon then tells the client by sending the Hello() reply that +it has made the setsockopt(2) call and that now is able to send +messages to other peers on the bus. + +Well-known names are represented by addresses in the 0x0001, ... prefixes. + +Addresses in prefix 0x0000 must be mapped to D-Bus unique names in a +way that can't collide with unique names allocated by the dbus-daemon +for legacy clients. + +In order to be consistent with current D-Bus unique naming, the AF_BUS +addresses can be mapped directly to D-Bus unique names, for example +(0000/0000deadbeef to ":0.deadbeef"). Leading zeroes can be suppressed +since the common case should be relatively small numbers (the kernel +allocates client addresses sequentially, and machines could be +rebooted occasionally). + +By having both AF_BUS and legacy D-Bus clients use the same address +space, the D-Bus daemon can act as a proxy between clients and can be +sure that D-Bus unique names will be unique for both AF_BUS and legacy +clients. + +To act as a proxy between AF_BUS and legacy clients, each time the +D-Bus daemon accepts a legacy connection (i.e., AF_UNIX), it will +create an AF_BUS socket and establish a connection with itself. It +will then associate this newly created connection with the legacy one. + +To explain it graphically: + + L The AF_BUS listening socket } + / | \ }-- listener process + A1 A2 A3 The AF_BUS accepted sockets } + | | | + C1 C2 C3 The AF_BUS connected sockets, where: + | * C1 belongs to the listener process + | * C2 and C3 belongs to the client processes + | + L2--A4 The AF_UNIX listening and accepted sockets \ + | in the listener process + C4 The AF_UNIX connected socket in the legacy client process + + +where C2 and C3 are normal AF_BUS clients and C4 is a legacy +client. The D-Bus daemon after accepting the connection using the +legacy transport (A4), will create an AF_BUS socket pair (C1, A1) +associated with the legacy client. + +Legacy clients will send messages to the D-Bus daemon using their +legacy socket and the D-Bus daemon will extract the destination +address, resolve to the corresponding AF_BUS address and use this to +send the message to the right peer. + +Conversely, when an AF_BUS client sends a D-Bus message to a legacy +client, it will use the AF_BUS address of the connection associated +with that client. The D-Bus daemon will receive the message, modify +the message's content to set SENDER headers based on the AF_BUS source +address and use the legacy transport to send the D-Bus message to the +legacy client. + +As a special case, the bus daemon's all-zeroes address maps to +"org.freedesktop.DBus" and vice versa. + +When a D-Bus client receives an AF_BUS message from the bus master +(0/0), it must use the SENDER header field in the D-Bus message, as +for any other D-Bus transport, to determine whether the message is +actually from the D-Bus daemon (the SENDER is "org.freedesktop.DBus" +or missing), or from another client (the SENDER starts with ":"). It +is valid for messages from another AF_BUS client to be received via +the D-Bus daemon; if they are, the SENDER header field will always be +set. + +Besides its unique name, D-Bus services can have well-known names such +as org.gnome.Keyring or org.freedesktop.Telepathy. These well-known +names can also be used as a D-Bus message destination +address. Well-known names are not numeric and AF_BUS is not able to +parse D-Bus messages. + +To solve this, the D-Bus daemon will assign an additional AF_BUS +address to each D-Bus client that owns a well-known name. The mapping +between well-known names and AF_BUS address is maintained by the D-Bus +daemon on a persistent data structure. + +D-Bus client libraries will maintain a cache of these mappings so they +can send messages to services with well-known names using their mapped +AF_BUS address. + +If a client intending to send a D-Bus message to a given well-known +name does not have that well-known name in its cache, it must send the +AF_BUS message to the listener (0000/000000000000) instead. + +The listener must forward the D-Bus message to the owner of that +well-known name, setting the SENDER header field if necessary. It may +also send this AF_BUS-specific D-Bus signal to the sender, so that the +sender can update its cache: + + org.freedesktop.DBus.AF_BUS.Forwarded (STRING well_known_name, + UINT64 af_bus_client) + + Emitted by the D-Bus daemon with sender "org.freedesktop.DBus" + and object path "/org/freedesktop/DBus" to indicate that + the well-known name well_known_name is represented by the + AF_BUS address { AF_BUS, af_bus_client, path } where + path is the path name used by this bus. + + For instance, if the well-known name "org.gnome.Keyring" + is represented by AF_BUS address 0001/0000deadbeef, + the signal would have arguments ("org.gnome.Keyring", + 0x00010000deadbeef), corresponding to the AF_BUS + address { AF_BUS, 0x00010000deadbeef, /tmp/test }. + +If the D-Bus service for that well-known name is not active, then the +D-Bus daemon will first do the service activation, assign an +additional address to the recently activated service, store the +well-known service to numeric address mapping on its persistent cache, +and then send the AF_BUS.Forwarded signal back to the client. + +Once the mapping has been made, the AF_BUS address associated with a +well-known name cannot be reused for the lifetime of the D-Bus daemon +(which is the same as the lifetime of the socket). + +Nevertheless the AF_BUS address associated with a well-known name can +change, for example if a service goes away and a new instance gets +activated. This new instance can have a different AF_BUS address. The +D-Bus daemon will maintain a list of the mappings that are currently +valid so it can send the AF_BUS. + +Forwarded signal with the mapping information to the clients. Client +libraries will maintain a fixed-size Last Recently Used (LRU) cache +with previous mappings sent by the D-Bus daemon. + +If the clients overwrite a mapping due to the LRU replace policy and +later want to send a D-Bus message to the overwritten well-known name, +they will send the D-Bus message back to the D-Bus daemon and this +will send the signal with the mapping information. + +If a service goes away or if the service AF_BUS address changed and +the client still has the old AF_BUS address in its cache, it will send +the D-Bus message to the old destination. + +Since packets whose destination AF_BUS addresses are not assigned to +any process are routed by default to the bus master, the D-Bus daemon +will receive these D-bus messages and send an AF_BUS. + +Forwarded signal back to the client with the new AF_BUS address so it +can update its cache with the new mapping. + +For well-known names, the D-Bus daemon will use a different address +prefix (0x0001) so it doesn't conflict with the D-Bus unique names +address prefix (0x0000). + +Besides D-Bus method call messages which are unicast, D-Bus allows +clients to send multicast messages (D-Bus signals). Clients can send +signals messages using the bus unique name prefix multicast address +(0x0001ffffffffffff). + +A netfilter hook is used to filter these multicast messages and only +deliver to the correct peers based on match rules. + + +D-Bus aware netfilter module +---------------------------- + +AF_BUS is designed to be a generic bus transport supporting both +unicast and multicast communications. + +In order for D-Bus to operate efficiently, the transport method has to +know the D-Bus message wire-protocol and D-Bus message structure. But +adding this D-Bus specific knowledge to AF_BUS will break one of the +fundamental design principles of any network protocol stack, namely +layer-independence: layer n must not make any assumptions about the +payload in layer n + 1. + +So, in order to have a clean protocol design but be able to allow the +transport to analyze the D-Bus messages, netfilter hooks are used to +do the filtering based on match rules. + +The kernel module has to maintain the match rules and the D-Bus daemon +is responsible for managing this information. Every time an add match +rule message is processed by the D-Bus daemon, this will update the +netfilter module match rules set so the netfilter hook function can +use that information to do the match rules based filtering. + +The D-Bus daemon and the netfilter module will use the generic netlink +subsystem to do the kernel-to-user-space communication. Netlink is +already used by most of the networking subsystem in Linux +(iptables/netfilter, ip/routing, etc). + +We enforce a security scheme so only the bus master's user ID can +update the netfilter module match rules set. + +The advantage of using the netfilter subsystem is that we decouple the +mechanism from the policy. AF_BUS will only add a set of hook points +and external modules will be used to enforce a given policy. diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c index 82fa4f0f91d6..c96678aea2fa 100644 --- a/drivers/connector/connector.c +++ b/drivers/connector/connector.c @@ -118,6 +118,38 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask) EXPORT_SYMBOL_GPL(cn_netlink_send); /* + * Send an unicast reply from a connector callback + * + */ +int cn_netlink_reply(struct cn_msg *msg, u32 pid, gfp_t gfp_mask) +{ + unsigned int size; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct cn_msg *data; + struct cn_dev *dev = &cdev; + + size = NLMSG_SPACE(sizeof(*msg) + msg->len); + + skb = alloc_skb(size, gfp_mask); + if (!skb) + return -ENOMEM; + + nlh = nlmsg_put(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh), 0); + if (nlh == NULL) { + kfree_skb(skb); + return -EMSGSIZE; + } + + data = nlmsg_data(nlh); + + memcpy(data, msg, sizeof(*data) + msg->len); + + return netlink_unicast(dev->nls, skb, pid, 1); +} +EXPORT_SYMBOL_GPL(cn_netlink_reply); + +/* * Callback helper - queues work and setup destructor for given data. */ static int cn_call_callback(struct sk_buff *skb) diff --git a/include/linux/bus.h b/include/linux/bus.h new file mode 100644 index 000000000000..19cac36727e1 --- /dev/null +++ b/include/linux/bus.h @@ -0,0 +1,34 @@ +#ifndef _LINUX_BUS_H +#define _LINUX_BUS_H + +#include <linux/socket.h> + +/* 'protocol' to use in socket(AF_BUS, SOCK_SEQPACKET, protocol) */ +#define BUS_PROTO_NONE 0 +#define BUS_PROTO_DBUS 1 +#define BUS_PROTO_MAX 1 + +#define BUS_PATH_MAX 108 + +/** + * struct bus_addr - af_bus address + * @s_addr: an af_bus address (16-bit prefix + 48-bit client address) + */ +struct bus_addr { + u64 s_addr; +}; + + +/** + * struct sockaddr_bus - af_bus socket address + * @sbus_family: the socket address family + * @sbus_addr: an af_bus address + * @sbus_path: a path name + */ +struct sockaddr_bus { + __kernel_sa_family_t sbus_family; + struct bus_addr sbus_addr; + char sbus_path[BUS_PATH_MAX]; +}; + +#endif /* _LINUX_BUS_H */ diff --git a/include/linux/connector.h b/include/linux/connector.h index 76384074262d..c27be606b5f0 100644 --- a/include/linux/connector.h +++ b/include/linux/connector.h @@ -125,6 +125,7 @@ int cn_add_callback(struct cb_id *id, const char *name, void (*callback)(struct cn_msg *, struct netlink_skb_parms *)); void cn_del_callback(struct cb_id *); int cn_netlink_send(struct cn_msg *, u32, gfp_t); +int cn_netlink_reply(struct cn_msg *, u32, gfp_t); int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name, struct cb_id *id, diff --git a/include/linux/security.h b/include/linux/security.h index d143b8e01954..ca961124159c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1577,6 +1577,8 @@ struct security_operations { #ifdef CONFIG_SECURITY_NETWORK int (*unix_stream_connect) (struct sock *sock, struct sock *other, struct sock *newsk); + int (*bus_connect) (struct sock *sock, struct sock *other, + struct sock *newsk); int (*unix_may_send) (struct socket *sock, struct socket *other); int (*socket_create) (int family, int type, int protocol, int kern); @@ -2518,6 +2520,8 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32 #ifdef CONFIG_SECURITY_NETWORK int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk); +int security_bus_connect(struct sock *sock, struct sock *other, + struct sock *newsk); int security_unix_may_send(struct socket *sock, struct socket *other); int security_socket_create(int family, int type, int protocol, int kern); int security_socket_post_create(struct socket *sock, int family, @@ -2565,6 +2569,13 @@ static inline int security_unix_stream_connect(struct sock *sock, return 0; } +static inline int security_bus_connect(struct socket *sock, + struct sock *other, + struct sock *newsk) +{ + return 0; +} + static inline int security_unix_may_send(struct socket *sock, struct socket *other) { diff --git a/include/linux/socket.h b/include/linux/socket.h index ba7b2e817cfa..7d6d2a4e2670 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -195,7 +195,8 @@ struct ucred { #define AF_CAIF 37 /* CAIF sockets */ #define AF_ALG 38 /* Algorithm sockets */ #define AF_NFC 39 /* NFC sockets */ -#define AF_MAX 40 /* For now.. */ +#define AF_BUS 40 /* BUS sockets */ +#define AF_MAX 41 /* For now.. */ /* Protocol families, same as address families. */ #define PF_UNSPEC AF_UNSPEC @@ -238,6 +239,7 @@ struct ucred { #define PF_CAIF AF_CAIF #define PF_ALG AF_ALG #define PF_NFC AF_NFC +#define PF_BUS AF_BUS #define PF_MAX AF_MAX /* Maximum queue length specifiable by listen. */ @@ -313,6 +315,7 @@ struct ucred { #define SOL_IUCV 277 #define SOL_CAIF 278 #define SOL_ALG 279 +#define SOL_BUS 280 /* IPX options */ #define IPX_TYPE 1 diff --git a/include/net/af_bus.h b/include/net/af_bus.h new file mode 100644 index 000000000000..e63eb49b6afb --- /dev/null +++ b/include/net/af_bus.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2012, GENIVI Alliance + * + * Authors: Javier Martinez Canillas, <javier.martinez@collabora.co.uk> + * Alban Crequy, <alban.crequy@collabora.co.uk> + * + * 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. + * + * Based on BSD Unix domain sockets (net/unix). + */ + +#ifndef __LINUX_NET_AFBUS_H +#define __LINUX_NET_AFBUS_H + +#include <linux/socket.h> +#include <linux/bus.h> +#include <linux/mutex.h> +#include <net/sock.h> +#include <net/tcp_states.h> + +extern void bus_inflight(struct file *fp); +extern void bus_notinflight(struct file *fp); +extern void bus_gc(void); +extern void wait_for_bus_gc(void); +extern struct sock *bus_get_socket(struct file *filp); +extern struct sock *bus_peer_get(struct sock *); + +#define BUS_HASH_SIZE 256 + +extern spinlock_t bus_address_lock; +extern struct hlist_head bus_address_table[BUS_HASH_SIZE]; + +#define BUS_MAX_QLEN 10 +#define BUS_MASTER_ADDR 0x0 +#define BUS_PREFIX_BITS 16 +#define BUS_CLIENT_BITS 48 +#define BUS_PREFIX_MASK 0xffff000000000000 +#define BUS_CLIENT_MASK 0x0000ffffffffffff + +/* AF_BUS socket options */ +#define BUS_ADD_ADDR 1 +#define BUS_JOIN_BUS 2 +#define BUS_DEL_ADDR 3 +#define BUS_SET_EAVESDROP 4 +#define BUS_UNSET_EAVESDROP 5 +#define BUS_SET_SENDBUF 6 +#define BUS_SET_MAXQLEN 7 +#define BUS_GET_QLENFULL 8 + +/* Connection and socket states */ +enum { + BUS_ESTABLISHED = TCP_ESTABLISHED, + BUS_CLOSE = TCP_CLOSE, + BUS_LISTEN = TCP_LISTEN, + BUS_MAX_STATES +}; + +#define NF_BUS_SENDING 1 + +extern unsigned int bus_tot_inflight; +extern spinlock_t bus_table_lock; +extern struct hlist_head bus_socket_table[BUS_HASH_SIZE + 1]; + +/** + * struct bus_address - an af_bus address associated with an af_bus sock + * @refcnt: address reference counter + * @len: address length + * @hash: address hash value + * @addr_node: member of struct bus_sock.addr_list + * @table_node: member of struct hlist_head bus_address_table[hash] + * @sock: the af_bus sock that owns this address + * @name: the socket address for this address + */ +struct bus_address { + atomic_t refcnt; + int len; + unsigned hash; + struct hlist_node addr_node; + struct hlist_node table_node; + struct sock *sock; + struct sockaddr_bus name[0]; +}; + +/** + * struct bus_send_context - sending context for an socket buffer + * @sender_socket: the sender socket associated with this sk_buff + * @siocb: used to send ancillary data + * @timeo: sending timeout + * @max_level: file descriptor passing maximum recursion level + * @namelen: length of socket address name + * @hash: socket name hash value + * @other: destination sock + * @sender: sender socket address name + * @recipient: recipient socket address name + * @authenticated: flag whether the sock already joined the bus + * @bus_master_side: flag whether the sock is an accepted socket + * @to_master: flag whether the destination is the bus master + * @multicast: flag whether the destination is a multicast address + * @deliver: flag whether the skb has to be delivered + * @eavesdropper: flag whether the sock is allowed to eavesdrop + * @main_recipient: flag whether the sock is the main recipient + */ +struct bus_send_context { + struct socket *sender_socket; + struct sock_iocb *siocb; + long timeo; + int max_level; + int namelen; + unsigned hash; + struct sock *other; + struct sockaddr_bus *sender; + struct sockaddr_bus *recipient; + unsigned int authenticated:1; + unsigned int bus_master_side:1; + unsigned int to_master:1; + unsigned int multicast:1; + unsigned int deliver:1; + unsigned int eavesdropper:1; + unsigned int main_recipient:1; +}; + +/** + * struct bus_skb_parms - socket buffer parameters + * @pid: process id + * @cred: skb credentials + * @fp: passed file descriptors + * @secid: security id + * @sendctx: skb sending context + */ +struct bus_skb_parms { + struct pid *pid; + const struct cred *cred; + struct scm_fp_list *fp; +#ifdef CONFIG_SECURITY_NETWORK + u32 secid; +#endif + struct bus_send_context *sendctx; +}; + +#define BUSCB(skb) (*(struct bus_skb_parms *)&((skb)->cb)) +#define BUSSID(skb) (&BUSCB((skb)).secid) + +#define bus_state_lock(s) spin_lock(&bus_sk(s)->lock) +#define bus_state_unlock(s) spin_unlock(&bus_sk(s)->lock) +#define bus_state_lock_nested(s) \ + spin_lock_nested(&bus_sk(s)->lock, \ + SINGLE_DEPTH_NESTING) + +/** + * struct bus - a communication bus + * @master: the bus master sock + * @peers: list of struct bus_sock.bus_node allowed to join the bus + * @lock: protect peers concurrent access + * @send_lock: enforce atomic multicast delivery + * @kref: bus reference counter + * @addr_cnt: address number counter to assign prefix 0x0000 addresses + * @eavesdropper_cnt: eavesdroppers counter + */ +struct bus { + struct sock *master; + struct hlist_head peers; + spinlock_t lock; + spinlock_t send_lock; + struct kref kref; + atomic64_t addr_cnt; + atomic64_t eavesdropper_cnt; +}; + +/** + * struct bus_sock - an af_bus socket + * @sk: associated sock + * @addr: sock principal address + * @addr_list: list of struct bus_address.addr_node + * @path: sock path name + * @readlock: protect from concurrent reading + * @peer: peer sock + * @other: the listening sock + * @link: list of candidates for garbage collection + * @inflight: number of times the file descriptor is in flight + * @lock: protect the sock from concurrent access + * @gc_candidate: flag whether the is a candidate for gc + * @gc_maybe_cycle: flag whether could be a cyclic reference + * @recursion_level: file passing current recursion level + * @peer_wq: peer sock wait queue + * @bus: bus that this sock belongs to + * @bus_master: flag whether the sock is the bus master + * @bus_master_side: flag whether is an accepted socket + * @authenticated: flag whether the sock joined the bus + * @eavesdropper: flag whether the sock is allowed to eavesdrop + * @bus_node: member of struct bus.peers list of joined socks + */ +struct bus_sock { + /* WARNING: sk has to be the first member */ + struct sock sk; + struct bus_address *addr; + struct hlist_head addr_list; + struct path path; + struct mutex readlock; + struct sock *peer; + struct sock *other; + struct list_head link; + atomic_long_t inflight; + spinlock_t lock; + unsigned int gc_candidate:1; + unsigned int gc_maybe_cycle:1; + unsigned char recursion_level; + struct socket_wq peer_wq; + struct bus *bus; + bool bus_master; + bool bus_master_side; + bool authenticated; + bool eavesdropper; + struct hlist_node bus_node; +}; +#define bus_sk(__sk) ((struct bus_sock *)__sk) + +#define peer_wait peer_wq.wait + +/** + * bus_same_bus - Test if two socket address belongs to the same bus + * @sbusaddr1: socket address name + * @sbusaddr2: socket address name + */ +static inline bool bus_same_bus(struct sockaddr_bus *sbusaddr1, + struct sockaddr_bus *sbusaddr2) +{ + int offset; + + if (sbusaddr1->sbus_path[0] != sbusaddr2->sbus_path[0]) + return false; + + /* + * abstract path names start with a null byte character, + * so they have to be compared starting at the second char. + */ + offset = (sbusaddr1->sbus_path[0] == '\0'); + + return !strncmp(sbusaddr1->sbus_path + offset, + sbusaddr2->sbus_path + offset, + BUS_PATH_MAX); +} + +static inline unsigned int bus_hash_fold(__wsum n) +{ + unsigned int hash = (__force unsigned int)n; + hash ^= hash>>16; + hash ^= hash>>8; + return hash&(BUS_HASH_SIZE-1); +} + +static inline unsigned int bus_compute_hash(struct bus_addr addr) +{ + return bus_hash_fold(csum_partial((void *)&addr, sizeof(addr), 0)); +} + +long bus_inq_len(struct sock *sk); +long bus_outq_len(struct sock *sk); + +#ifdef CONFIG_SYSCTL +extern int bus_sysctl_register(struct net *net); +extern void bus_sysctl_unregister(struct net *net); +#else +static inline int bus_sysctl_register(struct net *net) { return 0; } +static inline void bus_sysctl_unregister(struct net *net) {} +#endif + +bool bus_can_write(struct net *net, struct sockaddr_bus *addr, int len, + int protocol); + +#endif /* __LINUX_NET_AFBUS_H */ diff --git a/net/Kconfig b/net/Kconfig index 245831bec09a..339a6309d87e 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -47,6 +47,7 @@ menu "Networking options" source "net/packet/Kconfig" source "net/unix/Kconfig" +source "net/bus/Kconfig" source "net/xfrm/Kconfig" source "net/iucv/Kconfig" diff --git a/net/Makefile b/net/Makefile index 4f4ee083064c..ad0e90099fb6 100644 --- a/net/Makefile +++ b/net/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_NETFILTER) += netfilter/ obj-$(CONFIG_INET) += ipv4/ obj-$(CONFIG_XFRM) += xfrm/ obj-$(CONFIG_UNIX) += unix/ +obj-$(CONFIG_AF_BUS) += bus/ obj-$(CONFIG_NET) += ipv6/ obj-$(CONFIG_PACKET) += packet/ obj-$(CONFIG_NET_KEY) += key/ diff --git a/net/bus/Kconfig b/net/bus/Kconfig new file mode 100644 index 000000000000..4ec0beec30ea --- /dev/null +++ b/net/bus/Kconfig @@ -0,0 +1,17 @@ +# +# Bus Domain Sockets +# + +config AF_BUS + tristate "Bus domain sockets (EXPERIMENTAL)" + depends on EXPERIMENTAL + ---help--- + If you say Y here, you will include support for Bus domain sockets. + These sockets are used to create communication buses for IPC. + + To compile this driver as a module, choose M here: the module will be + 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 new file mode 100644 index 000000000000..a6ba26331ce2 --- /dev/null +++ b/net/bus/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the Linux bus domain socket layer. +# + +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/af_bus.c b/net/bus/af_bus.c new file mode 100644 index 000000000000..d25af65d79a0 --- /dev/null +++ b/net/bus/af_bus.c @@ -0,0 +1,2688 @@ +/* + * Implementation of Bus domain sockets. + * + * Copyright (c) 2012, GENIVI Alliance + * + * Authors: Javier Martinez Canillas <javier.martinez@collabora.co.uk> + * Alban Crequy <alban.crequy@collabora.co.uk> + * + * 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. + * + * Based on BSD Unix domain sockets (net/unix). + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/dcache.h> +#include <linux/namei.h> +#include <linux/socket.h> +#include <linux/bus.h> +#include <linux/fcntl.h> +#include <linux/termios.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/in.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <net/net_namespace.h> +#include <net/sock.h> +#include <net/af_bus.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <net/scm.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/rtnetlink.h> +#include <linux/mount.h> +#include <net/checksum.h> +#include <linux/security.h> + +struct hlist_head bus_socket_table[BUS_HASH_SIZE + 1]; +EXPORT_SYMBOL_GPL(bus_socket_table); +struct hlist_head bus_address_table[BUS_HASH_SIZE]; +EXPORT_SYMBOL_GPL(bus_address_table); +DEFINE_SPINLOCK(bus_table_lock); +DEFINE_SPINLOCK(bus_address_lock); +EXPORT_SYMBOL_GPL(bus_address_lock); +static atomic_long_t bus_nr_socks; + +#define bus_sockets_unbound (&bus_socket_table[BUS_HASH_SIZE]) + +#define BUS_ABSTRACT(sk) (bus_sk(sk)->addr->hash != BUS_HASH_SIZE) + +#ifdef CONFIG_SECURITY_NETWORK +static void bus_get_secdata(struct scm_cookie *scm, struct sk_buff *skb) +{ + memcpy(BUSSID(skb), &scm->secid, sizeof(u32)); +} + +static inline void bus_set_secdata(struct scm_cookie *scm, struct sk_buff *skb) +{ + scm->secid = *BUSSID(skb); +} +#else +static inline void bus_get_secdata(struct scm_cookie *scm, struct sk_buff *skb) +{ } + +static inline void bus_set_secdata(struct scm_cookie *scm, struct sk_buff *skb) +{ } +#endif /* CONFIG_SECURITY_NETWORK */ + +/* + * SMP locking strategy: + * bus_socket_table hash table is protected with spinlock bus_table_lock + * bus_address_table hash table is protected with spinlock bus_address_lock + * each bus is protected by a separate spin lock. + * multicast atomic sending is protected by a separate spin lock. + * each socket state is protected by a separate spin lock. + * each socket address is protected by a separate spin lock. + * + * When holding more than one lock, use the following hierarchy: + * - bus_table_lock. + * - bus_address_lock. + * - socket lock. + * - bus lock. + * - bus send_lock. + * - sock address lock. + */ + +#define bus_peer(sk) (bus_sk(sk)->peer) + +static inline int bus_our_peer(struct sock *sk, struct sock *osk) +{ + return bus_peer(osk) == sk; +} + +static inline int bus_recvq_full(struct sock const *sk) +{ + return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog; +} + +static inline u16 bus_addr_prefix(struct sockaddr_bus *busaddr) +{ + return (busaddr->sbus_addr.s_addr & BUS_PREFIX_MASK) >> BUS_CLIENT_BITS; +} + +static inline u64 bus_addr_client(struct sockaddr_bus *sbusaddr) +{ + return sbusaddr->sbus_addr.s_addr & BUS_CLIENT_MASK; +} + +static inline bool bus_mc_addr(struct sockaddr_bus *sbusaddr) +{ + return bus_addr_client(sbusaddr) == BUS_CLIENT_MASK; +} + +struct sock *bus_peer_get(struct sock *s) +{ + struct sock *peer; + + bus_state_lock(s); + peer = bus_peer(s); + if (peer) + sock_hold(peer); + bus_state_unlock(s); + return peer; +} +EXPORT_SYMBOL_GPL(bus_peer_get); + +static inline void bus_release_addr(struct bus_address *addr) +{ + if (atomic_dec_and_test(&addr->refcnt)) + kfree(addr); +} + +/* + * Check bus socket name: + * - should be not zero length. + * - if started by not zero, should be NULL terminated (FS object) + * - if started by zero, it is abstract name. + */ + +static int bus_mkname(struct sockaddr_bus *sbusaddr, int len, + unsigned int *hashp) +{ + int offset = (sbusaddr->sbus_path[0] == '\0'); + + if (len <= sizeof(short) || len > sizeof(*sbusaddr)) + return -EINVAL; + if (!sbusaddr || sbusaddr->sbus_family != AF_BUS) + return -EINVAL; + + len = strnlen(sbusaddr->sbus_path + offset, BUS_PATH_MAX) + 1 + + sizeof(__kernel_sa_family_t) + + sizeof(struct bus_addr); + + *hashp = bus_compute_hash(sbusaddr->sbus_addr); + return len; +} + +static void __bus_remove_address(struct bus_address *addr) +{ + hlist_del(&addr->table_node); +} + +static void __bus_insert_address(struct hlist_head *list, + struct bus_address *addr) +{ + hlist_add_head(&addr->table_node, list); +} + +static inline void bus_remove_address(struct bus_address *addr) +{ + spin_lock(&bus_address_lock); + __bus_remove_address(addr); + spin_unlock(&bus_address_lock); +} + +static inline void bus_insert_address(struct hlist_head *list, + struct bus_address *addr) +{ + spin_lock(&bus_address_lock); + __bus_insert_address(list, addr); + spin_unlock(&bus_address_lock); +} + +static void __bus_remove_socket(struct sock *sk) +{ + sk_del_node_init(sk); +} + +static void __bus_insert_socket(struct hlist_head *list, struct sock *sk) +{ + WARN_ON(!sk_unhashed(sk)); + sk_add_node(sk, list); +} + +static inline void bus_remove_socket(struct sock *sk) +{ + spin_lock(&bus_table_lock); + __bus_remove_socket(sk); + spin_unlock(&bus_table_lock); +} + +static inline void bus_insert_socket(struct hlist_head *list, struct sock *sk) +{ + spin_lock(&bus_table_lock); + __bus_insert_socket(list, sk); + spin_unlock(&bus_table_lock); +} + +static inline bool __bus_has_prefix(struct sock *sk, u16 prefix) +{ + struct bus_sock *u = bus_sk(sk); + struct bus_address *addr; + struct hlist_node *node; + bool ret = false; + + hlist_for_each_entry(addr, node, &u->addr_list, addr_node) { + if (bus_addr_prefix(addr->name) == prefix) + ret = true; + } + + return ret; +} + +static inline bool bus_has_prefix(struct sock *sk, u16 prefix) +{ + bool ret; + + bus_state_lock(sk); + ret = __bus_has_prefix(sk, prefix); + bus_state_unlock(sk); + + return ret; +} + +static inline bool __bus_eavesdropper(struct sock *sk, u16 condition) +{ + struct bus_sock *u = bus_sk(sk); + + return u->eavesdropper; +} + +static inline bool bus_eavesdropper(struct sock *sk, u16 condition) +{ + bool ret; + + bus_state_lock(sk); + ret = __bus_eavesdropper(sk, condition); + bus_state_unlock(sk); + + return ret; +} + +static inline bool bus_has_prefix_eavesdropper(struct sock *sk, u16 prefix) +{ + bool ret; + + bus_state_lock(sk); + ret = __bus_has_prefix(sk, prefix) || __bus_eavesdropper(sk, 0); + bus_state_unlock(sk); + + return ret; +} + +static inline struct bus_address *__bus_get_address(struct sock *sk, + struct bus_addr *sbus_addr) +{ + struct bus_sock *u = bus_sk(sk); + struct bus_address *addr = NULL; + struct hlist_node *node; + + hlist_for_each_entry(addr, node, &u->addr_list, addr_node) { + if (addr->name->sbus_addr.s_addr == sbus_addr->s_addr) + return addr; + } + + return NULL; +} + +static inline struct bus_address *bus_get_address(struct sock *sk, + struct bus_addr *sbus_addr) +{ + struct bus_address *addr; + + bus_state_lock(sk); + addr = __bus_get_address(sk, sbus_addr); + bus_state_unlock(sk); + + return addr; +} + +static struct sock *__bus_find_socket_byname(struct net *net, + struct sockaddr_bus *sbusname, + int len, unsigned int hash) +{ + struct sock *s; + struct hlist_node *node; + + sk_for_each(s, node, &bus_socket_table[hash]) { + struct bus_sock *u = bus_sk(s); + + if (!net_eq(sock_net(s), net)) + continue; + + if (u->addr->len == len && + !memcmp(u->addr->name, sbusname, len)) + return s; + } + + return NULL; +} + +static inline struct sock *bus_find_socket_byname(struct net *net, + struct sockaddr_bus *sbusname, + int len, unsigned int hash) +{ + struct sock *s; + + spin_lock(&bus_table_lock); + s = __bus_find_socket_byname(net, sbusname, len, hash); + if (s) + sock_hold(s); + spin_unlock(&bus_table_lock); + return s; +} + +static struct sock *__bus_find_socket_byaddress(struct net *net, + struct sockaddr_bus *sbusname, + int len, int protocol, + unsigned int hash) +{ + struct sock *s; + struct bus_address *addr; + struct hlist_node *node; + struct bus_sock *u; + int offset = (sbusname->sbus_path[0] == '\0'); + int path_len = strnlen(sbusname->sbus_path + offset, BUS_PATH_MAX); + + len = path_len + 1 + sizeof(__kernel_sa_family_t) + + sizeof(struct bus_addr); + + hlist_for_each_entry(addr, node, &bus_address_table[hash], + table_node) { + s = addr->sock; + u = bus_sk(s); + + if (s->sk_protocol != protocol) + 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 == sbusname->sbus_addr.s_addr + && bus_same_bus(addr->name, sbusname)) + goto found; + } + s = NULL; +found: + return s; +} + +static inline struct sock *bus_find_socket_byaddress(struct net *net, + struct sockaddr_bus *name, + int len, int protocol, + unsigned int hash) +{ + struct sock *s; + + spin_lock(&bus_address_lock); + s = __bus_find_socket_byaddress(net, name, len, protocol, hash); + if (s) + sock_hold(s); + spin_unlock(&bus_address_lock); + return s; +} + +static inline int bus_writable(struct sock *sk) +{ + return (atomic_read(&sk->sk_wmem_alloc) << 2) <= sk->sk_sndbuf; +} + +static void bus_write_space(struct sock *sk) +{ + struct bus_sock *u = bus_sk(sk); + struct bus_sock *p; + struct hlist_node *node; + struct socket_wq *wq; + + if (bus_writable(sk)) { + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + if (wq_has_sleeper(wq)) + wake_up_interruptible_sync_poll(&wq->wait, + POLLOUT | POLLWRNORM | POLLWRBAND); + sk_wake_async(sk, SOCK_WAKE_SPACE, POLL_OUT); + rcu_read_unlock(); + + if (u && u->bus) { + spin_lock(&u->bus->lock); + hlist_for_each_entry(p, node, &u->bus->peers, + bus_node) { + wake_up_interruptible_sync_poll(sk_sleep(&p->sk), + POLLOUT | + POLLWRNORM | + POLLWRBAND); + sk_wake_async(&p->sk, SOCK_WAKE_SPACE, + POLL_OUT); + } + spin_unlock(&u->bus->lock); + } + } +} + +static void bus_bus_release(struct kref *kref) +{ + struct bus *bus; + + bus = container_of(kref, struct bus, kref); + + kfree(bus); +} + +static void bus_sock_destructor(struct sock *sk) +{ + struct bus_sock *u = bus_sk(sk); + + skb_queue_purge(&sk->sk_receive_queue); + + WARN_ON(atomic_read(&sk->sk_wmem_alloc)); + WARN_ON(!sk_unhashed(sk)); + WARN_ON(sk->sk_socket); + if (!sock_flag(sk, SOCK_DEAD)) { + pr_info("Attempt to release alive bus socket: %p\n", sk); + return; + } + + if (u->bus) { + kref_put(&u->bus->kref, bus_bus_release); + u->bus = NULL; + } + + atomic_long_dec(&bus_nr_socks); + local_bh_disable(); + sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1); + local_bh_enable(); +#ifdef BUS_REFCNT_DEBUG + pr_debug("BUS %p is destroyed, %ld are still alive.\n", sk, + atomic_long_read(&bus_nr_socks)); +#endif +} + +static int bus_release_sock(struct sock *sk, int embrion) +{ + struct bus_sock *u = bus_sk(sk); + struct path path; + struct sock *skpair; + struct sk_buff *skb; + int state; + struct bus_address *addr; + struct hlist_node *node, *tmp; + + bus_remove_socket(sk); + + if (u->bus && u->authenticated && + !u->bus_master && !u->bus_master_side) { + spin_lock(&u->bus->lock); + hlist_del(&u->bus_node); + if (u->eavesdropper) + atomic64_dec(&u->bus->eavesdropper_cnt); + spin_unlock(&u->bus->lock); + } + + /* Clear state */ + bus_state_lock(sk); + sock_orphan(sk); + sk->sk_shutdown = SHUTDOWN_MASK; + path = u->path; + u->path.dentry = NULL; + u->path.mnt = NULL; + state = sk->sk_state; + sk->sk_state = BUS_CLOSE; + + if (u->bus_master) + u->bus->master = NULL; + + if (u->bus_master_side) { + bus_release_addr(u->addr); + u->addr = NULL; + } else { + u->addr = NULL; + + spin_lock(&bus_address_lock); + hlist_for_each_entry_safe(addr, node, tmp, &u->addr_list, + addr_node) { + hlist_del(&addr->addr_node); + __bus_remove_address(addr); + bus_release_addr(addr); + } + spin_unlock(&bus_address_lock); + } + + bus_state_unlock(sk); + + wake_up_interruptible_all(&u->peer_wait); + + skpair = bus_peer(sk); + + if (skpair != NULL) { + bus_state_lock(skpair); + /* No more writes */ + skpair->sk_shutdown = SHUTDOWN_MASK; + if (!skb_queue_empty(&sk->sk_receive_queue) || embrion) + skpair->sk_err = ECONNRESET; + bus_state_unlock(skpair); + skpair->sk_state_change(skpair); + sk_wake_async(skpair, SOCK_WAKE_WAITD, POLL_HUP); + sock_put(skpair); /* It may now die */ + bus_peer(sk) = NULL; + } + + /* Try to flush out this socket. Throw out buffers at least */ + + while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { + if (state == BUS_LISTEN) + bus_release_sock(skb->sk, 1); + /* passed fds are erased in the kfree_skb hook */ + kfree_skb(skb); + } + + if (path.dentry) + path_put(&path); + + sock_put(sk); + + /* ---- Socket is dead now and most probably destroyed ---- */ + + if (bus_tot_inflight) + bus_gc(); /* Garbage collect fds */ + + return 0; +} + +static void init_peercred(struct sock *sk) +{ + put_pid(sk->sk_peer_pid); + if (sk->sk_peer_cred) + put_cred(sk->sk_peer_cred); + sk->sk_peer_pid = get_pid(task_tgid(current)); + sk->sk_peer_cred = get_current_cred(); +} + +static void copy_peercred(struct sock *sk, struct sock *peersk) +{ + put_pid(sk->sk_peer_pid); + if (sk->sk_peer_cred) + put_cred(sk->sk_peer_cred); + sk->sk_peer_pid = get_pid(peersk->sk_peer_pid); + sk->sk_peer_cred = get_cred(peersk->sk_peer_cred); +} + +static int bus_listen(struct socket *sock, int backlog) +{ + int err; + struct sock *sk = sock->sk; + struct bus_sock *u = bus_sk(sk); + struct pid *old_pid = NULL; + const struct cred *old_cred = NULL; + + err = -EINVAL; + if (!u->addr || !u->bus_master) + goto out; /* Only listens on an bound an master socket */ + bus_state_lock(sk); + if (sk->sk_state != BUS_CLOSE && sk->sk_state != BUS_LISTEN) + goto out_unlock; + if (backlog > sk->sk_max_ack_backlog) + wake_up_interruptible_all(&u->peer_wait); + sk->sk_max_ack_backlog = backlog; + sk->sk_state = BUS_LISTEN; + /* set credentials so connect can copy them */ + init_peercred(sk); + err = 0; + +out_unlock: + bus_state_unlock(sk); + put_pid(old_pid); + if (old_cred) + put_cred(old_cred); +out: + return err; +} + +static int bus_release(struct socket *); +static int bus_bind(struct socket *, struct sockaddr *, int); +static int bus_connect(struct socket *, struct sockaddr *, + int addr_len, int flags); +static int bus_accept(struct socket *, struct socket *, int); +static int bus_getname(struct socket *, struct sockaddr *, int *, int); +static unsigned int bus_poll(struct file *, struct socket *, + poll_table *); +static int bus_ioctl(struct socket *, unsigned int, unsigned long); +static int bus_shutdown(struct socket *, int); +static int bus_setsockopt(struct socket *, int, int, char __user *, + unsigned int); +static int bus_sendmsg(struct kiocb *, struct socket *, + struct msghdr *, size_t); +static int bus_recvmsg(struct kiocb *, struct socket *, + struct msghdr *, size_t, int); + +static void bus_set_peek_off(struct sock *sk, int val) +{ + struct bus_sock *u = bus_sk(sk); + + mutex_lock(&u->readlock); + sk->sk_peek_off = val; + mutex_unlock(&u->readlock); +} + +static const struct proto_ops bus_seqpacket_ops = { + .family = PF_BUS, + .owner = THIS_MODULE, + .release = bus_release, + .bind = bus_bind, + .connect = bus_connect, + .socketpair = sock_no_socketpair, + .accept = bus_accept, + .getname = bus_getname, + .poll = bus_poll, + .ioctl = bus_ioctl, + .listen = bus_listen, + .shutdown = bus_shutdown, + .setsockopt = bus_setsockopt, + .getsockopt = sock_no_getsockopt, + .sendmsg = bus_sendmsg, + .recvmsg = bus_recvmsg, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, + .set_peek_off = bus_set_peek_off, +}; + +static struct proto bus_proto = { + .name = "BUS", + .owner = THIS_MODULE, + .obj_size = sizeof(struct bus_sock), +}; + +/* + * AF_BUS sockets do not interact with hardware, hence they + * dont trigger interrupts - so it's safe for them to have + * bh-unsafe locking for their sk_receive_queue.lock. Split off + * this special lock-class by reinitializing the spinlock key: + */ +static struct lock_class_key af_bus_sk_receive_queue_lock_key; + +static struct sock *bus_create1(struct net *net, struct socket *sock) +{ + struct sock *sk = NULL; + struct bus_sock *u; + + atomic_long_inc(&bus_nr_socks); + if (atomic_long_read(&bus_nr_socks) > 2 * get_max_files()) + goto out; + + sk = sk_alloc(net, PF_BUS, GFP_KERNEL, &bus_proto); + if (!sk) + goto out; + + sock_init_data(sock, sk); + lockdep_set_class(&sk->sk_receive_queue.lock, + &af_bus_sk_receive_queue_lock_key); + + sk->sk_write_space = bus_write_space; + sk->sk_max_ack_backlog = BUS_MAX_QLEN; + sk->sk_destruct = bus_sock_destructor; + u = bus_sk(sk); + u->path.dentry = NULL; + u->path.mnt = NULL; + u->bus = NULL; + u->bus_master = false; + u->authenticated = false; + u->eavesdropper = false; + spin_lock_init(&u->lock); + atomic_long_set(&u->inflight, 0); + INIT_LIST_HEAD(&u->link); + INIT_HLIST_HEAD(&u->addr_list); + INIT_HLIST_NODE(&u->bus_node); + mutex_init(&u->readlock); /* single task reading lock */ + init_waitqueue_head(&u->peer_wait); + bus_insert_socket(bus_sockets_unbound, sk); +out: + if (sk == NULL) + atomic_long_dec(&bus_nr_socks); + else { + local_bh_disable(); + sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); + local_bh_enable(); + } + return sk; +} + +static int bus_create(struct net *net, struct socket *sock, int protocol, + int kern) +{ + struct sock *sk; + + if (protocol < BUS_PROTO_NONE || protocol > BUS_PROTO_DBUS) + return -EPROTONOSUPPORT; + + if (protocol != BUS_PROTO_NONE) + request_module("net-pf-%d-proto-%d", PF_BUS, protocol); + + sock->state = SS_UNCONNECTED; + + if (sock->type == SOCK_SEQPACKET) + sock->ops = &bus_seqpacket_ops; + else + return -ESOCKTNOSUPPORT; + + sk = bus_create1(net, sock); + if (!sk) + return -ENOMEM; + + sk->sk_protocol = protocol; + + return 0; +} + +static int bus_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (!sk) + return 0; + + sock->sk = NULL; + + return bus_release_sock(sk, 0); +} + +static struct sock *bus_find_other(struct net *net, + struct sockaddr_bus *sbusname, int len, + int protocol, unsigned int hash, int *error) +{ + struct sock *u; + struct path path; + int err = 0; + + if (sbusname->sbus_path[0]) { + struct inode *inode; + err = kern_path(sbusname->sbus_path, LOOKUP_FOLLOW, &path); + if (err) + goto fail; + inode = path.dentry->d_inode; + err = inode_permission(inode, MAY_WRITE); + if (err) + goto put_fail; + + err = -ECONNREFUSED; + if (!S_ISSOCK(inode->i_mode)) + goto put_fail; + u = bus_find_socket_byaddress(net, sbusname, len, protocol, + hash); + if (!u) + goto put_fail; + + touch_atime(&path); + path_put(&path); + + } else { + err = -ECONNREFUSED; + u = bus_find_socket_byaddress(net, sbusname, len, protocol, hash); + if (u) { + struct dentry *dentry; + dentry = bus_sk(u)->path.dentry; + if (dentry) + touch_atime(&bus_sk(u)->path); + } else + goto fail; + } + + return u; + +put_fail: + path_put(&path); +fail: + *error = err; + return NULL; +} + + +static int bus_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) +{ + struct sock *sk = sock->sk; + struct net *net = sock_net(sk); + struct bus_sock *u = bus_sk(sk); + struct sockaddr_bus *sbusaddr = (struct sockaddr_bus *)uaddr; + char *sbus_path = sbusaddr->sbus_path; + struct dentry *dentry = NULL; + struct path path; + int err; + unsigned int hash; + struct bus_address *addr; + struct hlist_head *list; + struct bus *bus; + + err = -EINVAL; + if (sbusaddr->sbus_family != AF_BUS) + goto out; + + /* If the address is available, the socket is the bus master */ + sbusaddr->sbus_addr.s_addr = BUS_MASTER_ADDR; + + err = bus_mkname(sbusaddr, addr_len, &hash); + if (err < 0) + goto out; + addr_len = err; + + mutex_lock(&u->readlock); + + err = -EINVAL; + if (u->addr) + goto out_up; + + err = -ENOMEM; + addr = kzalloc(sizeof(*addr) + sizeof(struct sockaddr_bus), GFP_KERNEL); + if (!addr) + goto out_up; + + memcpy(addr->name, sbusaddr, sizeof(struct sockaddr_bus)); + addr->len = addr_len; + addr->hash = hash; + atomic_set(&addr->refcnt, 1); + addr->sock = sk; + INIT_HLIST_NODE(&addr->addr_node); + INIT_HLIST_NODE(&addr->table_node); + + if (sbus_path[0]) { + umode_t mode; + err = 0; + /* + * Get the parent directory, calculate the hash for last + * component. + */ + dentry = kern_path_create(AT_FDCWD, sbus_path, &path, 0); + err = PTR_ERR(dentry); + if (IS_ERR(dentry)) + goto out_mknod_parent; + + /* + * All right, let's create it. + */ + mode = S_IFSOCK | + (SOCK_INODE(sock)->i_mode & ~current_umask()); + err = mnt_want_write(path.mnt); + if (err) + goto out_mknod_dput; + err = security_path_mknod(&path, dentry, mode, 0); + if (err) + goto out_mknod_drop_write; + err = vfs_mknod(path.dentry->d_inode, dentry, mode, 0); +out_mknod_drop_write: + mnt_drop_write(path.mnt); + if (err) + goto out_mknod_dput; + mutex_unlock(&path.dentry->d_inode->i_mutex); + dput(path.dentry); + path.dentry = dentry; + } + + err = -ENOMEM; + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) + goto out_unlock; + + spin_lock(&bus_table_lock); + + if (!sbus_path[0]) { + err = -EADDRINUSE; + if (__bus_find_socket_byname(net, sbusaddr, addr_len, hash)) { + bus_release_addr(addr); + kfree(bus); + goto out_unlock; + } + + list = &bus_socket_table[addr->hash]; + } else { + list = &bus_socket_table[dentry->d_inode->i_ino & + (BUS_HASH_SIZE-1)]; + u->path = path; + } + + kref_init(&bus->kref); + bus->master = sk; + INIT_HLIST_HEAD(&bus->peers); + spin_lock_init(&bus->lock); + spin_lock_init(&bus->send_lock); + atomic64_set(&bus->addr_cnt, 0); + atomic64_set(&bus->eavesdropper_cnt, 0); + + hlist_add_head(&addr->addr_node, &u->addr_list); + + err = 0; + __bus_remove_socket(sk); + u->addr = addr; + u->bus_master = true; + u->bus = bus; + __bus_insert_socket(list, sk); + bus_insert_address(&bus_address_table[addr->hash], addr); + +out_unlock: + spin_unlock(&bus_table_lock); +out_up: + mutex_unlock(&u->readlock); +out: + return err; + +out_mknod_dput: + dput(dentry); + mutex_unlock(&path.dentry->d_inode->i_mutex); + path_put(&path); +out_mknod_parent: + if (err == -EEXIST) + err = -EADDRINUSE; + bus_release_addr(addr); + goto out_up; +} + +static long bus_wait_for_peer(struct sock *other, long timeo) +{ + struct bus_sock *u = bus_sk(other); + int sched; + DEFINE_WAIT(wait); + + prepare_to_wait_exclusive(&u->peer_wait, &wait, TASK_INTERRUPTIBLE); + + sched = !sock_flag(other, SOCK_DEAD) && + !(other->sk_shutdown & RCV_SHUTDOWN) && + bus_recvq_full(other); + + bus_state_unlock(other); + + if (sched) + timeo = schedule_timeout(timeo); + + finish_wait(&u->peer_wait, &wait); + return timeo; +} + +static int bus_connect(struct socket *sock, struct sockaddr *uaddr, + int addr_len, int flags) +{ + struct sockaddr_bus *sbusaddr = (struct sockaddr_bus *)uaddr; + struct sock *sk = sock->sk; + struct net *net = sock_net(sk); + struct bus_sock *u = bus_sk(sk), *newu, *otheru; + struct sock *newsk = NULL; + struct sock *other = NULL; + struct sk_buff *skb = NULL; + struct bus_address *addr = NULL; + unsigned int hash; + int st; + int err; + long timeo; + + /* Only connections to the bus master is allowed */ + sbusaddr->sbus_addr.s_addr = BUS_MASTER_ADDR; + + err = bus_mkname(sbusaddr, addr_len, &hash); + if (err < 0) + goto out; + addr_len = err; + + err = -ENOMEM; + addr = kzalloc(sizeof(*addr) + sizeof(struct sockaddr_bus), GFP_KERNEL); + if (!addr) + goto out; + + atomic_set(&addr->refcnt, 1); + INIT_HLIST_NODE(&addr->addr_node); + INIT_HLIST_NODE(&addr->table_node); + + timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); + + /* First of all allocate resources. + If we will make it after state is locked, + we will have to recheck all again in any case. + */ + + err = -ENOMEM; + + /* create new sock for complete connection */ + newsk = bus_create1(sock_net(sk), NULL); + if (newsk == NULL) + goto out; + + /* Allocate skb for sending to listening sock */ + skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL); + if (skb == NULL) + goto out; + +restart: + /* Find listening sock. */ + other = bus_find_other(net, sbusaddr, addr_len, sk->sk_protocol, hash, + &err); + if (!other) + goto out; + + /* Latch state of peer */ + bus_state_lock(other); + + /* Apparently VFS overslept socket death. Retry. */ + if (sock_flag(other, SOCK_DEAD)) { + bus_state_unlock(other); + sock_put(other); + goto restart; + } + + err = -ECONNREFUSED; + if (other->sk_state != BUS_LISTEN) + goto out_unlock; + if (other->sk_shutdown & RCV_SHUTDOWN) + goto out_unlock; + + if (bus_recvq_full(other)) { + err = -EAGAIN; + if (!timeo) + goto out_unlock; + + timeo = bus_wait_for_peer(other, timeo); + + err = sock_intr_errno(timeo); + if (signal_pending(current)) + goto out; + sock_put(other); + goto restart; + } + + /* Latch our state. + + It is tricky place. We need to grab our state lock and cannot + drop lock on peer. It is dangerous because deadlock is + possible. Connect to self case and simultaneous + attempt to connect are eliminated by checking socket + state. other is BUS_LISTEN, if sk is BUS_LISTEN we + check this before attempt to grab lock. + + Well, and we have to recheck the state after socket locked. + */ + st = sk->sk_state; + + switch (st) { + case BUS_CLOSE: + /* This is ok... continue with connect */ + break; + case BUS_ESTABLISHED: + /* Socket is already connected */ + err = -EISCONN; + goto out_unlock; + default: + err = -EINVAL; + goto out_unlock; + } + + bus_state_lock_nested(sk); + + if (sk->sk_state != st) { + bus_state_unlock(sk); + bus_state_unlock(other); + sock_put(other); + goto restart; + } + + err = security_bus_connect(sk, other, newsk); + if (err) { + bus_state_unlock(sk); + goto out_unlock; + } + + /* The way is open! Fastly set all the necessary fields... */ + + sock_hold(sk); + bus_peer(newsk) = sk; + newsk->sk_state = BUS_ESTABLISHED; + newsk->sk_type = sk->sk_type; + newsk->sk_protocol = sk->sk_protocol; + init_peercred(newsk); + newu = bus_sk(newsk); + RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq); + otheru = bus_sk(other); + + /* copy address information from listening to new sock*/ + if (otheru->addr && otheru->bus_master) { + atomic_inc(&otheru->addr->refcnt); + newu->addr = otheru->addr; + memcpy(addr->name, otheru->addr->name, + sizeof(struct sockaddr_bus)); + addr->len = otheru->addr->len; + addr->name->sbus_addr.s_addr = + (atomic64_inc_return(&otheru->bus->addr_cnt) & + BUS_CLIENT_MASK); + addr->hash = bus_compute_hash(addr->name->sbus_addr); + addr->sock = sk; + u->addr = addr; + kref_get(&otheru->bus->kref); + u->bus = otheru->bus; + u->bus_master_side = false; + kref_get(&otheru->bus->kref); + newu->bus = otheru->bus; + newu->bus_master_side = true; + hlist_add_head(&addr->addr_node, &u->addr_list); + + bus_insert_address(&bus_address_table[addr->hash], addr); + } + if (otheru->path.dentry) { + path_get(&otheru->path); + newu->path = otheru->path; + } + + /* Set credentials */ + copy_peercred(sk, other); + sk->sk_sndbuf = other->sk_sndbuf; + sk->sk_max_ack_backlog = other->sk_max_ack_backlog; + newsk->sk_sndbuf = other->sk_sndbuf; + + sock->state = SS_CONNECTED; + sk->sk_state = BUS_ESTABLISHED; + sock_hold(newsk); + + smp_mb__after_atomic_inc(); /* sock_hold() does an atomic_inc() */ + bus_peer(sk) = newsk; + + bus_state_unlock(sk); + + /* take ten and and send info to listening sock */ + spin_lock(&other->sk_receive_queue.lock); + __skb_queue_tail(&other->sk_receive_queue, skb); + spin_unlock(&other->sk_receive_queue.lock); + bus_state_unlock(other); + other->sk_data_ready(other, 0); + sock_put(other); + return 0; + +out_unlock: + if (other) + bus_state_unlock(other); + +out: + kfree_skb(skb); + if (addr) + bus_release_addr(addr); + if (newsk) + bus_release_sock(newsk, 0); + if (other) + sock_put(other); + return err; +} + +static int bus_accept(struct socket *sock, struct socket *newsock, int flags) +{ + struct sock *sk = sock->sk; + struct sock *tsk; + struct sk_buff *skb; + int err; + + err = -EINVAL; + if (sk->sk_state != BUS_LISTEN) + goto out; + + /* If socket state is BUS_LISTEN it cannot change (for now...), + * so that no locks are necessary. + */ + + skb = skb_recv_datagram(sk, 0, flags&O_NONBLOCK, &err); + if (!skb) { + /* This means receive shutdown. */ + if (err == 0) + err = -EINVAL; + goto out; + } + + tsk = skb->sk; + skb_free_datagram(sk, skb); + wake_up_interruptible(&bus_sk(sk)->peer_wait); + + /* attach accepted sock to socket */ + bus_state_lock(tsk); + newsock->state = SS_CONNECTED; + sock_graft(tsk, newsock); + bus_state_unlock(tsk); + return 0; + +out: + return err; +} + + +static int bus_getname(struct socket *sock, struct sockaddr *uaddr, + int *uaddr_len, int peer) +{ + struct sock *sk = sock->sk; + struct bus_sock *u; + DECLARE_SOCKADDR(struct sockaddr_bus *, sbusaddr, uaddr); + int err = 0; + + if (peer) { + sk = bus_peer_get(sk); + + err = -ENOTCONN; + if (!sk) + goto out; + err = 0; + } else { + sock_hold(sk); + } + + u = bus_sk(sk); + + bus_state_lock(sk); + if (!u->addr) { + sbusaddr->sbus_family = AF_BUS; + sbusaddr->sbus_path[0] = 0; + *uaddr_len = sizeof(short); + } else { + struct bus_address *addr = u->addr; + + *uaddr_len = sizeof(struct sockaddr_bus); + memcpy(sbusaddr, addr->name, *uaddr_len); + } + bus_state_unlock(sk); + sock_put(sk); +out: + return err; +} + +static void bus_detach_fds(struct scm_cookie *scm, struct sk_buff *skb) +{ + int i; + + scm->fp = BUSCB(skb).fp; + BUSCB(skb).fp = NULL; + + for (i = scm->fp->count-1; i >= 0; i--) + bus_notinflight(scm->fp->fp[i]); +} + +static void bus_destruct_scm(struct sk_buff *skb) +{ + struct scm_cookie scm; + memset(&scm, 0, sizeof(scm)); + scm.pid = BUSCB(skb).pid; + scm.cred = BUSCB(skb).cred; + if (BUSCB(skb).fp) + bus_detach_fds(&scm, skb); + + scm_destroy(&scm); + if (skb->sk) + sock_wfree(skb); +} + +#define MAX_RECURSION_LEVEL 4 + +static int bus_attach_fds(struct scm_cookie *scm, struct sk_buff *skb) +{ + int i; + unsigned char max_level = 0; + int bus_sock_count = 0; + + for (i = scm->fp->count - 1; i >= 0; i--) { + struct sock *sk = bus_get_socket(scm->fp->fp[i]); + + if (sk) { + bus_sock_count++; + max_level = max(max_level, + bus_sk(sk)->recursion_level); + } + } + if (unlikely(max_level > MAX_RECURSION_LEVEL)) + return -ETOOMANYREFS; + + /* + * Need to duplicate file references for the sake of garbage + * collection. Otherwise a socket in the fps might become a + * candidate for GC while the skb is not yet queued. + */ + BUSCB(skb).fp = scm_fp_dup(scm->fp); + if (!BUSCB(skb).fp) + return -ENOMEM; + + if (bus_sock_count) { + for (i = scm->fp->count - 1; i >= 0; i--) + bus_inflight(scm->fp->fp[i]); + } + return max_level; +} + +static int bus_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, + bool send_fds) +{ + int err = 0; + + BUSCB(skb).pid = get_pid(scm->pid); + if (scm->cred) + BUSCB(skb).cred = get_cred(scm->cred); + BUSCB(skb).fp = NULL; + if (scm->fp && send_fds) + err = bus_attach_fds(scm, skb); + + skb->destructor = bus_destruct_scm; + return err; +} + +/* + * Some apps rely on write() giving SCM_CREDENTIALS + * We include credentials if source or destination socket + * asserted SOCK_PASSCRED. + */ +static void maybe_add_creds(struct sk_buff *skb, const struct socket *sock, + const struct sock *other) +{ + if (BUSCB(skb).cred) + return; + if (test_bit(SOCK_PASSCRED, &sock->flags) || + !other->sk_socket || + test_bit(SOCK_PASSCRED, &other->sk_socket->flags)) { + BUSCB(skb).pid = get_pid(task_tgid(current)); + BUSCB(skb).cred = get_current_cred(); + } +} + +/* + * Send AF_BUS data. + */ + +static void bus_deliver_skb(struct sk_buff *skb) +{ + struct bus_send_context *sendctx = BUSCB(skb).sendctx; + struct socket *sock = sendctx->sender_socket; + + if (sock_flag(sendctx->other, SOCK_RCVTSTAMP)) + __net_timestamp(skb); + maybe_add_creds(skb, sock, sendctx->other); + skb_queue_tail(&sendctx->other->sk_receive_queue, skb); + if (sendctx->max_level > bus_sk(sendctx->other)->recursion_level) + bus_sk(sendctx->other)->recursion_level = sendctx->max_level; +} + +/** + * bus_sendmsg_finish - delivery an skb to a destination + * @skb: sk_buff to deliver + * + * Delivers a packet to a destination. The skb control buffer has + * all the information about the destination contained on sending + * context. If the sending is unicast, then the skb is delivered + * and the receiver notified but if the sending is multicast, the + * skb is just marked as delivered and the actual delivery is made + * outside the function with the bus->send_lock held to ensure that + * the multicast sending is atomic. + */ +static int bus_sendmsg_finish(struct sk_buff *skb) +{ + int err; + struct bus_send_context *sendctx; + struct socket *sock; + struct sock *sk; + struct net *net; + size_t len = skb->len; + + sendctx = BUSCB(skb).sendctx; + sock = sendctx->sender_socket; + sk = sock->sk; + net = sock_net(sk); + +restart: + if (!sendctx->other) { + err = -ECONNRESET; + if (sendctx->recipient == NULL) + goto out_free; + + sendctx->other = bus_find_other(net, sendctx->recipient, + sendctx->namelen, + sk->sk_protocol, + sendctx->hash, &err); + + if (sendctx->other == NULL || + !bus_sk(sendctx->other)->authenticated) { + + if (sendctx->other) + sock_put(sendctx->other); + + if (!bus_sk(sk)->bus_master_side) { + err = -ENOTCONN; + sendctx->other = bus_peer_get(sk); + if (!sendctx->other) + goto out_free; + } else { + sendctx->other = sk; + sock_hold(sendctx->other); + } + } + } + + if (sk_filter(sendctx->other, skb) < 0) { + /* Toss the packet but do not return any error to the sender */ + err = len; + goto out_free; + } + + bus_state_lock(sendctx->other); + + if (sock_flag(sendctx->other, SOCK_DEAD)) { + /* + * Check with 1003.1g - what should + * datagram error + */ + bus_state_unlock(sendctx->other); + sock_put(sendctx->other); + + err = 0; + bus_state_lock(sk); + if (bus_peer(sk) == sendctx->other) { + bus_peer(sk) = NULL; + bus_state_unlock(sk); + sock_put(sendctx->other); + err = -ECONNREFUSED; + } else { + bus_state_unlock(sk); + } + + sendctx->other = NULL; + if (err) + goto out_free; + goto restart; + } + + err = -EPIPE; + if (sendctx->other->sk_shutdown & RCV_SHUTDOWN) + goto out_unlock; + + if (bus_recvq_full(sendctx->other)) { + if (!sendctx->timeo) { + err = -EAGAIN; + goto out_unlock; + } + + sendctx->timeo = bus_wait_for_peer(sendctx->other, + sendctx->timeo); + + err = sock_intr_errno(sendctx->timeo); + if (signal_pending(current)) + goto out_free; + + goto restart; + } + + if (!sendctx->multicast && !sendctx->eavesdropper) { + bus_deliver_skb(skb); + bus_state_unlock(sendctx->other); + sendctx->other->sk_data_ready(sendctx->other, 0); + sock_put(sendctx->other); + } else { + sendctx->deliver = 1; + bus_state_unlock(sendctx->other); + } + + return len; + +out_unlock: + bus_state_unlock(sendctx->other); +out_free: + kfree_skb(skb); + if (sendctx->other) + sock_put(sendctx->other); + + return err; +} + +/** + * bus_sendmsg_mcast - do a multicast sending + * @skb: sk_buff to deliver + * + * Send a packet to a multicast destination. + * The function is also called for unicast sending when eavesdropping + * is enabled. Since the unicast destination and the eavesdroppers + * have to receive the packet atomically. + */ +static int bus_sendmsg_mcast(struct sk_buff *skb) +{ + struct bus_send_context *sendctx; + struct bus_send_context *tmpctx; + struct socket *sock; + struct sock *sk; + struct net *net; + struct bus_sock *u, *s; + struct hlist_node *node; + u16 prefix = 0; + struct sk_buff **skb_set = NULL; + struct bus_send_context **sendctx_set = NULL; + int rcp_cnt, send_cnt; + int i; + int err; + int len = skb->len; + bool (*is_receiver) (struct sock *, u16); + bool main_rcp_found = false; + + sendctx = BUSCB(skb).sendctx; + sendctx->deliver = 0; + sock = sendctx->sender_socket; + sk = sock->sk; + u = bus_sk(sk); + net = sock_net(sk); + + if (sendctx->multicast) { + prefix = bus_addr_prefix(sendctx->recipient); + if (sendctx->eavesdropper) + is_receiver = &bus_has_prefix_eavesdropper; + else + is_receiver = &bus_has_prefix; + } else { + is_receiver = &bus_eavesdropper; + + /* + * If the destination is not the peer accepted socket + * we have to get the correct destination. + */ + if (!sendctx->to_master && sendctx->recipient) { + sendctx->other = bus_find_other(net, sendctx->recipient, + sendctx->namelen, + sk->sk_protocol, + sendctx->hash, &err); + + + if (sendctx->other == NULL || + !bus_sk(sendctx->other)->authenticated) { + + if (sendctx->other) + sock_put(sendctx->other); + + if (sendctx->other == NULL) { + if (!bus_sk(sk)->bus_master_side) { + err = -ENOTCONN; + sendctx->other = bus_peer_get(sk); + if (!sendctx->other) + goto out; + } else { + sendctx->other = sk; + sock_hold(sendctx->other); + } + } + sendctx->to_master = 1; + } + } + } + + +try_again: + rcp_cnt = 0; + main_rcp_found = false; + + spin_lock(&u->bus->lock); + + hlist_for_each_entry(s, node, &u->bus->peers, bus_node) { + + if (!net_eq(sock_net(&s->sk), net)) + continue; + + if (is_receiver(&s->sk, prefix) || + (!sendctx->multicast && + !sendctx->to_master && + &s->sk == sendctx->other)) + rcp_cnt++; + } + + spin_unlock(&u->bus->lock); + + /* + * Memory can't be allocated while holding a spinlock so + * we have to release the lock, do the allocation for the + * array to store each destination peer sk_buff and grab + * the bus peer lock again. Peers could have joined the + * bus while we relesed the lock so we allocate 5 more + * recipients hoping that this will be enough to not having + * to try again in case only a few peers joined the bus. + */ + rcp_cnt += 5; + skb_set = kzalloc(sizeof(struct sk_buff *) * rcp_cnt, GFP_KERNEL); + + if (!skb_set) { + err = -ENOMEM; + goto out; + } + + sendctx_set = kzalloc(sizeof(struct bus_send_context *) * rcp_cnt, + GFP_KERNEL); + if (!sendctx_set) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < rcp_cnt; i++) { + skb_set[i] = skb_clone(skb, GFP_KERNEL); + if (!skb_set[i]) { + err = -ENOMEM; + goto out_free; + } + sendctx_set[i] = BUSCB(skb_set[i]).sendctx + = kmalloc(sizeof(*sendctx) * rcp_cnt, GFP_KERNEL); + if (!sendctx_set[i]) { + err = -ENOMEM; + goto out_free; + } + memcpy(sendctx_set[i], sendctx, sizeof(*sendctx)); + err = bus_scm_to_skb(sendctx_set[i]->siocb->scm, + skb_set[i], true); + if (err < 0) + goto out_free; + bus_get_secdata(sendctx_set[i]->siocb->scm, + skb_set[i]); + + sendctx_set[i]->other = NULL; + } + + send_cnt = 0; + + spin_lock(&u->bus->lock); + + hlist_for_each_entry(s, node, &u->bus->peers, bus_node) { + + if (!net_eq(sock_net(&s->sk), net)) + continue; + + if (send_cnt >= rcp_cnt) { + spin_unlock(&u->bus->lock); + + for (i = 0; i < rcp_cnt; i++) { + sock_put(sendctx_set[i]->other); + kfree_skb(skb_set[i]); + kfree(sendctx_set[i]); + } + kfree(skb_set); + kfree(sendctx_set); + sendctx_set = NULL; + skb_set = NULL; + goto try_again; + } + + if (is_receiver(&s->sk, prefix) || + (!sendctx->multicast && + !sendctx->to_master && + &s->sk == sendctx->other)) { + skb_set_owner_w(skb_set[send_cnt], &s->sk); + tmpctx = BUSCB(skb_set[send_cnt]).sendctx; + sock_hold(&s->sk); + if (&s->sk == sendctx->other) { + tmpctx->main_recipient = 1; + main_rcp_found = true; + } + tmpctx->other = &s->sk; + tmpctx->recipient = s->addr->name; + tmpctx->eavesdropper = bus_eavesdropper(&s->sk, 0); + + send_cnt++; + } + } + + spin_unlock(&u->bus->lock); + + /* + * Peers have left the bus so we have to free + * their pre-allocated bus_send_context and + * socket buffers. + */ + if (send_cnt < rcp_cnt) { + for (i = send_cnt; i < rcp_cnt; i++) { + kfree_skb(skb_set[i]); + kfree(sendctx_set[i]); + } + rcp_cnt = send_cnt; + } + + for (i = 0; i < send_cnt; i++) { + tmpctx = BUSCB(skb_set[i]).sendctx; + tmpctx->deliver = 0; + err = NF_HOOK(NFPROTO_BUS, NF_BUS_SENDING, skb_set[i], + NULL, NULL, bus_sendmsg_finish); + if (err == -EPERM) + sock_put(tmpctx->other); + } + + /* + * If the send context is not multicast, the destination + * coud be either the peer accepted socket descriptor or + * a peer that is not an eavesdropper. If the peer is not + * the accepted socket descriptor and has been authenticated, + * it is a member of the bus peer list so it has already been + * marked for delivery. + * But if the destination is the accepted socket descriptor + * or is a non-authenticated peer it is not a member of the + * bus peer list so the packet has to be explicitly deliver + * to it. + */ + + if (!sendctx->multicast && + (sendctx->to_master || + (sendctx->bus_master_side && !main_rcp_found))) { + sendctx->main_recipient = 1; + err = NF_HOOK(NFPROTO_BUS, NF_BUS_SENDING, skb, NULL, NULL, + bus_sendmsg_finish); + if (err == -EPERM) + sock_put(sendctx->other); + } + + spin_lock(&u->bus->send_lock); + + for (i = 0; i < send_cnt; i++) { + tmpctx = sendctx_set[i]; + if (tmpctx->deliver != 1) + continue; + + bus_state_lock(tmpctx->other); + bus_deliver_skb(skb_set[i]); + bus_state_unlock(tmpctx->other); + } + + if (!sendctx->multicast && + sendctx->deliver == 1 && + !bus_sk(sendctx->other)->eavesdropper) { + bus_state_lock(sendctx->other); + bus_deliver_skb(skb); + bus_state_unlock(sendctx->other); + } + + spin_unlock(&u->bus->send_lock); + + for (i = 0; i < send_cnt; i++) { + tmpctx = sendctx_set[i]; + if (tmpctx->deliver != 1) + continue; + + tmpctx->other->sk_data_ready(tmpctx->other, 0); + sock_put(tmpctx->other); + } + + if (!sendctx->multicast && + sendctx->deliver == 1 && + !bus_sk(sendctx->other)->eavesdropper) { + sendctx->other->sk_data_ready(sendctx->other, 0); + sock_put(sendctx->other); + } + + err = len; + goto out; + +out_free: + for (i = 0; i < rcp_cnt; i++) { + if (skb_set[i]) + kfree_skb(skb_set[i]); + } + +out: + kfree(skb_set); + if (sendctx_set) { + for (i = 0; i < rcp_cnt; i++) + kfree(sendctx_set[i]); + kfree(sendctx_set); + } + + if (sendctx->deliver == 0) { + if (!sendctx->to_master && + !(sendctx->bus_master_side && !main_rcp_found)) + kfree_skb(skb); + if (!sendctx->to_master && + !(sendctx->bus_master_side && !main_rcp_found)) + if (sendctx->other) + sock_put(sendctx->other); + } + scm_destroy(sendctx->siocb->scm); + + return err; +} + +static inline void bus_copy_path(struct sockaddr_bus *dest, + struct sockaddr_bus *src) +{ + int offset; + + /* + * abstract path names start with a null byte character, + * so they have to be compared starting at the second char. + */ + offset = (src->sbus_path[0] == '\0'); + + strncpy(dest->sbus_path + offset, + src->sbus_path + offset, + BUS_PATH_MAX); +} + +/** + * bus_sendmsg - send an skb to a destination + * @kiocb: I/O control block info + * @sock: sender socket + * @msg: message header + * @len: message length + * + * Send an socket buffer to a destination. The destination could be + * either an unicast or a multicast address. In any case, a copy of + * the packet has to be send to all the sockets that are allowed to + * eavesdrop the communication bus. + * + * If the destination address is not associated with any socket, the + * packet is default routed to the bus master (the sender accepted + * socket). + * + * The af_bus sending path is hooked to the netfilter subsystem so + * netfilter hooks can filter or modify the packet before delivery. + */ +static int bus_sendmsg(struct kiocb *kiocb, struct socket *sock, + struct msghdr *msg, size_t len) +{ + struct sock *sk = sock->sk; + struct bus_sock *u = bus_sk(sk); + struct sockaddr_bus *sbusaddr = msg->msg_name; + int err; + struct sk_buff *skb; + struct scm_cookie tmp_scm; + bool to_master = false; + bool multicast = false; + struct bus_send_context sendctx; + + err = sock_error(sk); + if (err) + return err; + + if (sk->sk_state != BUS_ESTABLISHED) + return -ENOTCONN; + + if (!msg->msg_namelen) + sbusaddr = NULL; + + if (sbusaddr) + bus_copy_path(sbusaddr, u->addr->name); + + if ((!sbusaddr && !u->bus_master_side) || + (sbusaddr && sbusaddr->sbus_addr.s_addr == BUS_MASTER_ADDR)) + to_master = true; + else if (sbusaddr && !u->bus_master_side && !u->authenticated) + return -EHOSTUNREACH; + + sendctx.namelen = 0; /* fake GCC */ + sendctx.siocb = kiocb_to_siocb(kiocb); + sendctx.other = NULL; + + if (NULL == sendctx.siocb->scm) + sendctx.siocb->scm = &tmp_scm; + wait_for_bus_gc(); + err = scm_send(sock, msg, sendctx.siocb->scm, false); + if (err < 0) + return err; + + err = -EOPNOTSUPP; + if (msg->msg_flags&MSG_OOB) + goto out; + + if (sbusaddr && !to_master) { + err = bus_mkname(sbusaddr, msg->msg_namelen, &sendctx.hash); + if (err < 0) + goto out; + sendctx.namelen = err; + multicast = bus_mc_addr(sbusaddr); + } else { + err = -ENOTCONN; + sendctx.other = bus_peer_get(sk); + if (!sendctx.other) + goto out; + } + + err = -EMSGSIZE; + if (len > sk->sk_sndbuf - 32) + goto out; + + sendctx.timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT); + +restart: + bus_state_lock(sk); + if (bus_recvq_full(sk)) { + err = -EAGAIN; + if (!sendctx.timeo) { + bus_state_unlock(sk); + goto out; + } + + sendctx.timeo = bus_wait_for_peer(sk, sendctx.timeo); + + err = sock_intr_errno(sendctx.timeo); + if (signal_pending(current)) + goto out; + + goto restart; + } else { + bus_state_unlock(sk); + } + + skb = sock_alloc_send_skb(sk, len, msg->msg_flags&MSG_DONTWAIT, &err); + if (skb == NULL) + goto out; + + err = bus_scm_to_skb(sendctx.siocb->scm, skb, true); + if (err < 0) + goto out_free; + sendctx.max_level = err + 1; + bus_get_secdata(sendctx.siocb->scm, skb); + + skb_reset_transport_header(skb); + err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len); + if (err) + goto out_free; + + sendctx.sender_socket = sock; + if (u->bus_master_side && sendctx.other) { + /* if the bus master sent an unicast message to a peer, we + * need the address of that peer + */ + sendctx.sender = bus_sk(sendctx.other)->addr->name; + } else { + sendctx.sender = u->addr->name; + } + sendctx.recipient = sbusaddr; + sendctx.authenticated = u->authenticated; + sendctx.bus_master_side = u->bus_master_side; + sendctx.to_master = to_master; + sendctx.multicast = multicast; + sendctx.eavesdropper = atomic64_read(&u->bus->eavesdropper_cnt) ? 1 : 0; + BUSCB(skb).sendctx = &sendctx; + + if (sendctx.multicast || sendctx.eavesdropper) { + sendctx.main_recipient = 0; + err = bus_sendmsg_mcast(skb); + return sendctx.multicast ? len : err; + } else { + sendctx.main_recipient = 1; + len = NF_HOOK(NFPROTO_BUS, NF_BUS_SENDING, skb, NULL, NULL, + bus_sendmsg_finish); + + if (len == -EPERM) { + err = len; + goto out; + } else { + scm_destroy(sendctx.siocb->scm); + return len; + } + } + +out_free: + kfree_skb(skb); +out: + if (sendctx.other) + sock_put(sendctx.other); + scm_destroy(sendctx.siocb->scm); + return err; +} + +static void bus_copy_addr(struct msghdr *msg, struct sock *sk) +{ + struct bus_sock *u = bus_sk(sk); + + msg->msg_namelen = 0; + if (u->addr) { + msg->msg_namelen = u->addr->len; + memcpy(msg->msg_name, u->addr->name, + sizeof(struct sockaddr_bus)); + } +} + +static int bus_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *msg, size_t size, int flags) +{ + struct sock_iocb *siocb = kiocb_to_siocb(iocb); + struct scm_cookie tmp_scm; + struct sock *sk = sock->sk; + struct bus_sock *u = bus_sk(sk); + int noblock = flags & MSG_DONTWAIT; + struct sk_buff *skb; + int err; + int peeked, skip; + + if (sk->sk_state != BUS_ESTABLISHED) + return -ENOTCONN; + + err = -EOPNOTSUPP; + if (flags&MSG_OOB) + goto out; + + msg->msg_namelen = 0; + + err = mutex_lock_interruptible(&u->readlock); + if (err) { + err = sock_intr_errno(sock_rcvtimeo(sk, noblock)); + goto out; + } + + skip = sk_peek_offset(sk, flags); + + skb = __skb_recv_datagram(sk, flags, &peeked, &skip, &err); + if (!skb) { + bus_state_lock(sk); + /* Signal EOF on disconnected non-blocking SEQPACKET socket. */ + if (err == -EAGAIN && (sk->sk_shutdown & RCV_SHUTDOWN)) + err = 0; + bus_state_unlock(sk); + goto out_unlock; + } + + wake_up_interruptible_sync_poll(&u->peer_wait, + POLLOUT | POLLWRNORM | POLLWRBAND); + + if (msg->msg_name) + bus_copy_addr(msg, skb->sk); + + if (size > skb->len - skip) + size = skb->len - skip; + else if (size < skb->len - skip) + msg->msg_flags |= MSG_TRUNC; + + err = skb_copy_datagram_iovec(skb, skip, msg->msg_iov, size); + if (err) + goto out_free; + + if (sock_flag(sk, SOCK_RCVTSTAMP)) + __sock_recv_timestamp(msg, sk, skb); + + if (!siocb->scm) { + siocb->scm = &tmp_scm; + memset(&tmp_scm, 0, sizeof(tmp_scm)); + } + scm_set_cred(siocb->scm, BUSCB(skb).pid, BUSCB(skb).cred); + bus_set_secdata(siocb->scm, skb); + + if (!(flags & MSG_PEEK)) { + if (BUSCB(skb).fp) + bus_detach_fds(siocb->scm, skb); + + sk_peek_offset_bwd(sk, skb->len); + } else { + /* It is questionable: on PEEK we could: + - do not return fds - good, but too simple 8) + - return fds, and do not return them on read (old strategy, + apparently wrong) + - clone fds (I chose it for now, it is the most universal + solution) + + POSIX 1003.1g does not actually define this clearly + at all. POSIX 1003.1g doesn't define a lot of things + clearly however! + + */ + + sk_peek_offset_fwd(sk, size); + + if (BUSCB(skb).fp) + siocb->scm->fp = scm_fp_dup(BUSCB(skb).fp); + } + err = (flags & MSG_TRUNC) ? skb->len - skip : size; + + scm_recv(sock, msg, siocb->scm, flags); + +out_free: + skb_free_datagram(sk, skb); +out_unlock: + mutex_unlock(&u->readlock); +out: + return err; +} + +static int bus_shutdown(struct socket *sock, int mode) +{ + struct sock *sk = sock->sk; + struct sock *other; + + mode = (mode+1)&(RCV_SHUTDOWN|SEND_SHUTDOWN); + + if (!mode) + return 0; + + bus_state_lock(sk); + sk->sk_shutdown |= mode; + other = bus_peer(sk); + if (other) + sock_hold(other); + bus_state_unlock(sk); + sk->sk_state_change(sk); + + if (other) { + + int peer_mode = 0; + + if (mode&RCV_SHUTDOWN) + peer_mode |= SEND_SHUTDOWN; + if (mode&SEND_SHUTDOWN) + peer_mode |= RCV_SHUTDOWN; + bus_state_lock(other); + other->sk_shutdown |= peer_mode; + bus_state_unlock(other); + other->sk_state_change(other); + if (peer_mode == SHUTDOWN_MASK) + sk_wake_async(other, SOCK_WAKE_WAITD, POLL_HUP); + else if (peer_mode & RCV_SHUTDOWN) + sk_wake_async(other, SOCK_WAKE_WAITD, POLL_IN); + sock_put(other); + } + + return 0; +} + +static int bus_add_addr(struct sock *sk, struct bus_addr *sbus_addr) +{ + struct bus_address *addr; + struct sock *other; + struct bus_sock *u = bus_sk(sk); + struct net *net = sock_net(sk); + int ret = 0; + + addr = kzalloc(sizeof(*addr) + sizeof(struct sockaddr_bus), GFP_KERNEL); + if (!addr) { + ret = -ENOMEM; + goto out; + } + + memcpy(addr->name, u->addr->name, sizeof(struct sockaddr_bus)); + addr->len = u->addr->len; + + addr->name->sbus_addr.s_addr = sbus_addr->s_addr; + addr->hash = bus_compute_hash(addr->name->sbus_addr); + other = bus_find_socket_byaddress(net, addr->name, addr->len, + sk->sk_protocol, addr->hash); + + if (other) { + sock_put(other); + kfree(addr); + ret = -EADDRINUSE; + goto out; + } + + atomic_set(&addr->refcnt, 1); + INIT_HLIST_NODE(&addr->addr_node); + INIT_HLIST_NODE(&addr->table_node); + + addr->sock = sk; + + hlist_add_head(&addr->addr_node, &u->addr_list); + bus_insert_address(&bus_address_table[addr->hash], addr); + +out: + sock_put(sk); + + return ret; +} + +static int bus_del_addr(struct sock *sk, struct bus_addr *sbus_addr) +{ + struct bus_address *addr; + int ret = 0; + + bus_state_lock(sk); + addr = __bus_get_address(sk, sbus_addr); + if (!addr) { + ret = -EINVAL; + bus_state_unlock(sk); + goto out; + } + hlist_del(&addr->addr_node); + bus_state_unlock(sk); + + bus_remove_address(addr); + bus_release_addr(addr); +out: + sock_put(sk); + + return ret; +} + +static int bus_join_bus(struct sock *sk) +{ + struct sock *peer; + struct bus_sock *u = bus_sk(sk), *peeru; + int err = 0; + + peer = bus_peer_get(sk); + if (!peer) + return -ENOTCONN; + peeru = bus_sk(peer); + + if (!u->bus_master_side || peeru->authenticated) { + err = -EINVAL; + goto sock_put_out; + } + + if (sk->sk_state != BUS_ESTABLISHED) { + err = -ENOTCONN; + goto sock_put_out; + } + + if (peer->sk_shutdown != 0) { + err = -ENOTCONN; + goto sock_put_out; + } + + bus_state_lock(peer); + peeru->authenticated = true; + bus_state_unlock(peer); + + spin_lock(&u->bus->lock); + hlist_add_head(&peeru->bus_node, &u->bus->peers); + spin_unlock(&u->bus->lock); + +sock_put_out: + sock_put(peer); + return err; +} + +static int __bus_set_eavesdrop(struct sock *sk, bool eavesdrop) +{ + struct sock *peer = bus_peer_get(sk); + struct bus_sock *u = bus_sk(sk), *peeru; + int err = 0; + + if (!peer) + return -ENOTCONN; + + if (sk->sk_state != BUS_ESTABLISHED) { + err = -ENOTCONN; + goto sock_put_out; + } + + peeru = bus_sk(peer); + + if (!u->bus_master_side || !peeru->authenticated) { + err = -EINVAL; + goto sock_put_out; + } + + if (peer->sk_shutdown != 0) { + err = -ENOTCONN; + goto sock_put_out; + } + + bus_state_lock(peeru); + if (peeru->eavesdropper != eavesdrop) { + peeru->eavesdropper = eavesdrop; + if (eavesdrop) + atomic64_inc(&u->bus->eavesdropper_cnt); + else + atomic64_dec(&u->bus->eavesdropper_cnt); + } + bus_state_unlock(peeru); + +sock_put_out: + sock_put(peer); + return err; +} + +static int bus_set_eavesdrop(struct sock *sk) +{ + return __bus_set_eavesdrop(sk, true); +} + +static int bus_unset_eavesdrop(struct sock *sk) +{ + return __bus_set_eavesdrop(sk, false); +} + +static inline void sk_sendbuf_set(struct sock *sk, int sndbuf) +{ + bus_state_lock(sk); + sk->sk_sndbuf = sndbuf; + bus_state_unlock(sk); +} + +static inline void sk_maxqlen_set(struct sock *sk, int qlen) +{ + bus_state_lock(sk); + sk->sk_max_ack_backlog = qlen; + bus_state_unlock(sk); +} + +static int bus_get_qlenfull(struct sock *sk) +{ + struct sock *peer; + struct bus_sock *u = bus_sk(sk), *peeru; + int ret = 0; + + peer = bus_peer_get(sk); + if (!peer) + return -ENOTCONN; + + peeru = bus_sk(peer); + + if (!u->bus_master_side || peeru->authenticated) { + ret = -EINVAL; + goto sock_put_out; + } + + if (sk->sk_state != BUS_ESTABLISHED) { + ret = -ENOTCONN; + goto sock_put_out; + } + + if (peer->sk_shutdown != 0) { + ret = -ENOTCONN; + goto sock_put_out; + } + + ret = bus_recvq_full(peer); + +sock_put_out: + sock_put(peer); + return ret; +} + +static int bus_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, unsigned int optlen) +{ + struct bus_addr addr; + int res; + int val; + + if (level != SOL_BUS) + return -ENOPROTOOPT; + + switch (optname) { + case BUS_ADD_ADDR: + case BUS_DEL_ADDR: + if (optlen < sizeof(struct bus_addr)) + return -EINVAL; + + if (!bus_sk(sock->sk)->bus_master_side) + return -EINVAL; + + if (copy_from_user(&addr, optval, sizeof(struct bus_addr))) + return -EFAULT; + + if (optname == BUS_ADD_ADDR) + res = bus_add_addr(bus_peer_get(sock->sk), &addr); + else + res = bus_del_addr(bus_peer_get(sock->sk), &addr); + break; + case BUS_JOIN_BUS: + res = bus_join_bus(sock->sk); + break; + case BUS_SET_EAVESDROP: + res = bus_set_eavesdrop(sock->sk); + break; + case BUS_UNSET_EAVESDROP: + res = bus_unset_eavesdrop(sock->sk); + break; + case BUS_SET_SENDBUF: + case BUS_SET_MAXQLEN: + if (sock->sk->sk_state != BUS_LISTEN) { + res = -EINVAL; + } else { + res = -EFAULT; + + if (copy_from_user(&val, optval, optlen)) + break; + + res = 0; + + if (optname == BUS_SET_SENDBUF) + sk_sendbuf_set(sock->sk, val); + else + sk_maxqlen_set(sock->sk, val); + } + break; + case BUS_GET_QLENFULL: + res = bus_get_qlenfull(sock->sk); + + if (copy_to_user(&res, optval, optlen)) { + res = -EFAULT; + break; + } + res = 0; + break; + default: + res = -EINVAL; + break; + } + + return res; +} + +long bus_inq_len(struct sock *sk) +{ + struct sk_buff *skb; + long amount = 0; + + if (sk->sk_state == BUS_LISTEN) + return -EINVAL; + + spin_lock(&sk->sk_receive_queue.lock); + skb_queue_walk(&sk->sk_receive_queue, skb) + amount += skb->len; + spin_unlock(&sk->sk_receive_queue.lock); + + return amount; +} +EXPORT_SYMBOL_GPL(bus_inq_len); + +long bus_outq_len(struct sock *sk) +{ + return sk_wmem_alloc_get(sk); +} +EXPORT_SYMBOL_GPL(bus_outq_len); + +static int bus_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + long amount = 0; + int err; + + switch (cmd) { + case SIOCOUTQ: + amount = bus_outq_len(sk); + err = put_user(amount, (int __user *)arg); + break; + case SIOCINQ: + amount = bus_inq_len(sk); + if (amount < 0) + err = amount; + else + err = put_user(amount, (int __user *)arg); + break; + default: + err = -ENOIOCTLCMD; + break; + } + return err; +} + +static unsigned int bus_poll(struct file *file, struct socket *sock, + poll_table *wait) +{ + struct sock *sk = sock->sk, *other; + unsigned int mask, writable; + struct bus_sock *u = bus_sk(sk), *p; + struct hlist_node *node; + + sock_poll_wait(file, sk_sleep(sk), wait); + mask = 0; + + /* exceptional events? */ + if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue)) + mask |= POLLERR; + if (sk->sk_shutdown & RCV_SHUTDOWN) + mask |= POLLRDHUP | POLLIN | POLLRDNORM; + if (sk->sk_shutdown == SHUTDOWN_MASK) + mask |= POLLHUP; + + /* readable? */ + if (!skb_queue_empty(&sk->sk_receive_queue)) + mask |= POLLIN | POLLRDNORM; + + /* Connection-based need to check for termination and startup */ + if (sk->sk_state == BUS_CLOSE) + mask |= POLLHUP; + + /* No write status requested, avoid expensive OUT tests. */ + if (!(poll_requested_events(wait) & (POLLWRBAND|POLLWRNORM|POLLOUT))) + return mask; + + writable = bus_writable(sk); + other = bus_peer_get(sk); + if (other) { + if (bus_recvq_full(other)) + writable = 0; + sock_put(other); + } + + /* + * If the socket has already joined the bus we have to check + * that each peer receiver queue on the bus is not full. + */ + if (!u->bus_master_side && u->authenticated) { + spin_lock(&u->bus->lock); + hlist_for_each_entry(p, node, &u->bus->peers, bus_node) { + if (bus_recvq_full(&p->sk)) { + writable = 0; + break; + } + } + spin_unlock(&u->bus->lock); + } + + if (writable) + mask |= POLLOUT | POLLWRNORM | POLLWRBAND; + else + set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags); + + return mask; +} + +#ifdef CONFIG_PROC_FS +static struct sock *first_bus_socket(int *i) +{ + for (*i = 0; *i <= BUS_HASH_SIZE; (*i)++) { + if (!hlist_empty(&bus_socket_table[*i])) + return __sk_head(&bus_socket_table[*i]); + } + return NULL; +} + +static struct sock *next_bus_socket(int *i, struct sock *s) +{ + struct sock *next = sk_next(s); + /* More in this chain? */ + if (next) + return next; + /* Look for next non-empty chain. */ + for ((*i)++; *i <= BUS_HASH_SIZE; (*i)++) { + if (!hlist_empty(&bus_socket_table[*i])) + return __sk_head(&bus_socket_table[*i]); + } + return NULL; +} + +struct bus_iter_state { + struct seq_net_private p; + int i; +}; + +static struct sock *bus_seq_idx(struct seq_file *seq, loff_t pos) +{ + struct bus_iter_state *iter = seq->private; + loff_t off = 0; + struct sock *s; + + for (s = first_bus_socket(&iter->i); s; + s = next_bus_socket(&iter->i, s)) { + if (sock_net(s) != seq_file_net(seq)) + continue; + if (off == pos) + return s; + ++off; + } + return NULL; +} + +static void *bus_seq_start(struct seq_file *seq, loff_t *pos) + __acquires(bus_table_lock) +{ + spin_lock(&bus_table_lock); + return *pos ? bus_seq_idx(seq, *pos - 1) : SEQ_START_TOKEN; +} + +static void *bus_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct bus_iter_state *iter = seq->private; + struct sock *sk = v; + ++*pos; + + if (v == SEQ_START_TOKEN) + sk = first_bus_socket(&iter->i); + else + sk = next_bus_socket(&iter->i, sk); + while (sk && (sock_net(sk) != seq_file_net(seq))) + sk = next_bus_socket(&iter->i, sk); + return sk; +} + +static void bus_seq_stop(struct seq_file *seq, void *v) + __releases(bus_table_lock) +{ + spin_unlock(&bus_table_lock); +} + +static int bus_seq_show(struct seq_file *seq, void *v) +{ + + if (v == SEQ_START_TOKEN) + seq_puts(seq, "Num RefCount Protocol Flags Type St " \ + "Inode Path\n"); + else { + struct sock *s = v; + struct bus_sock *u = bus_sk(s); + bus_state_lock(s); + + seq_printf(seq, "%pK: %08X %08X %08X %04X %02X %5lu", + s, + atomic_read(&s->sk_refcnt), + 0, + s->sk_state == BUS_LISTEN ? __SO_ACCEPTCON : 0, + s->sk_type, + s->sk_socket ? + (s->sk_state == BUS_ESTABLISHED ? SS_CONNECTED : SS_UNCONNECTED) : + (s->sk_state == BUS_ESTABLISHED ? SS_CONNECTING : SS_DISCONNECTING), + sock_i_ino(s)); + + if (u->addr) { + int i, len; + seq_putc(seq, ' '); + + i = 0; + len = u->addr->len - sizeof(short); + if (!BUS_ABSTRACT(s)) + len--; + else { + seq_putc(seq, '@'); + i++; + } + for ( ; i < len; i++) + seq_putc(seq, u->addr->name->sbus_path[i]); + } + bus_state_unlock(s); + seq_putc(seq, '\n'); + } + + return 0; +} + +static const struct seq_operations bus_seq_ops = { + .start = bus_seq_start, + .next = bus_seq_next, + .stop = bus_seq_stop, + .show = bus_seq_show, +}; + +static int bus_seq_open(struct inode *inode, struct file *file) +{ + return seq_open_net(inode, file, &bus_seq_ops, + sizeof(struct bus_iter_state)); +} + +static const struct file_operations bus_seq_fops = { + .owner = THIS_MODULE, + .open = bus_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_net, +}; + +#endif + +static const struct net_proto_family bus_family_ops = { + .family = PF_BUS, + .create = bus_create, + .owner = THIS_MODULE, +}; + +static int __init af_bus_init(void) +{ + int rc = -1; + struct sk_buff *dummy_skb; + + BUILD_BUG_ON(sizeof(struct bus_skb_parms) > sizeof(dummy_skb->cb)); + + rc = proto_register(&bus_proto, 1); + if (rc != 0) { + pr_crit("%s: Cannot create bus_sock SLAB cache!\n", __func__); + return rc; + } + + sock_register(&bus_family_ops); + return rc; +} + +static void __exit af_bus_exit(void) +{ + sock_unregister(PF_BUS); + proto_unregister(&bus_proto); +} + +module_init(af_bus_init); +module_exit(af_bus_exit); + +MODULE_AUTHOR("Alban Crequy, Javier Martinez Canillas"); +MODULE_DESCRIPTION("Linux Bus domain sockets"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_NETPROTO(PF_BUS); diff --git a/net/bus/garbage.c b/net/bus/garbage.c new file mode 100644 index 000000000000..2435f38a0849 --- /dev/null +++ b/net/bus/garbage.c @@ -0,0 +1,322 @@ +/* + * Garbage Collector For AF_BUS sockets + * + * Based on Garbage Collector For AF_UNIX sockets (net/unix/garbage.c). + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/socket.h> +#include <linux/un.h> +#include <linux/net.h> +#include <linux/fs.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/file.h> +#include <linux/proc_fs.h> +#include <linux/mutex.h> +#include <linux/wait.h> + +#include <net/sock.h> +#include <net/af_bus.h> +#include <net/scm.h> +#include <net/tcp_states.h> + +/* Internal data structures and random procedures: */ + +static LIST_HEAD(gc_inflight_list); +static LIST_HEAD(gc_candidates); +static DEFINE_SPINLOCK(bus_gc_lock); +static DECLARE_WAIT_QUEUE_HEAD(bus_gc_wait); + +unsigned int bus_tot_inflight; + + +struct sock *bus_get_socket(struct file *filp) +{ + struct sock *u_sock = NULL; + struct inode *inode = filp->f_path.dentry->d_inode; + + /* + * Socket ? + */ + if (S_ISSOCK(inode->i_mode) && !(filp->f_mode & FMODE_PATH)) { + struct socket *sock = SOCKET_I(inode); + struct sock *s = sock->sk; + + /* + * PF_BUS ? + */ + if (s && sock->ops && sock->ops->family == PF_BUS) + u_sock = s; + } + return u_sock; +} + +/* + * Keep the number of times in flight count for the file + * descriptor if it is for an AF_BUS socket. + */ + +void bus_inflight(struct file *fp) +{ + struct sock *s = bus_get_socket(fp); + if (s) { + struct bus_sock *u = bus_sk(s); + spin_lock(&bus_gc_lock); + if (atomic_long_inc_return(&u->inflight) == 1) { + BUG_ON(!list_empty(&u->link)); + list_add_tail(&u->link, &gc_inflight_list); + } else { + BUG_ON(list_empty(&u->link)); + } + bus_tot_inflight++; + spin_unlock(&bus_gc_lock); + } +} + +void bus_notinflight(struct file *fp) +{ + struct sock *s = bus_get_socket(fp); + if (s) { + struct bus_sock *u = bus_sk(s); + spin_lock(&bus_gc_lock); + BUG_ON(list_empty(&u->link)); + if (atomic_long_dec_and_test(&u->inflight)) + list_del_init(&u->link); + bus_tot_inflight--; + spin_unlock(&bus_gc_lock); + } +} + +static void scan_inflight(struct sock *x, void (*func)(struct bus_sock *), + struct sk_buff_head *hitlist) +{ + struct sk_buff *skb; + struct sk_buff *next; + + spin_lock(&x->sk_receive_queue.lock); + skb_queue_walk_safe(&x->sk_receive_queue, skb, next) { + /* + * Do we have file descriptors ? + */ + if (BUSCB(skb).fp) { + bool hit = false; + /* + * Process the descriptors of this socket + */ + int nfd = BUSCB(skb).fp->count; + struct file **fp = BUSCB(skb).fp->fp; + while (nfd--) { + /* + * Get the socket the fd matches + * if it indeed does so + */ + struct sock *sk = bus_get_socket(*fp++); + if (sk) { + struct bus_sock *u = bus_sk(sk); + + /* + * Ignore non-candidates, they could + * have been added to the queues after + * starting the garbage collection + */ + if (u->gc_candidate) { + hit = true; + func(u); + } + } + } + if (hit && hitlist != NULL) { + __skb_unlink(skb, &x->sk_receive_queue); + __skb_queue_tail(hitlist, skb); + } + } + } + spin_unlock(&x->sk_receive_queue.lock); +} + +static void scan_children(struct sock *x, void (*func)(struct bus_sock *), + struct sk_buff_head *hitlist) +{ + if (x->sk_state != TCP_LISTEN) + scan_inflight(x, func, hitlist); + else { + struct sk_buff *skb; + struct sk_buff *next; + struct bus_sock *u; + LIST_HEAD(embryos); + + /* + * For a listening socket collect the queued embryos + * and perform a scan on them as well. + */ + spin_lock(&x->sk_receive_queue.lock); + skb_queue_walk_safe(&x->sk_receive_queue, skb, next) { + u = bus_sk(skb->sk); + + /* + * An embryo cannot be in-flight, so it's safe + * to use the list link. + */ + BUG_ON(!list_empty(&u->link)); + list_add_tail(&u->link, &embryos); + } + spin_unlock(&x->sk_receive_queue.lock); + + while (!list_empty(&embryos)) { + u = list_entry(embryos.next, struct bus_sock, link); + scan_inflight(&u->sk, func, hitlist); + list_del_init(&u->link); + } + } +} + +static void dec_inflight(struct bus_sock *usk) +{ + atomic_long_dec(&usk->inflight); +} + +static void inc_inflight(struct bus_sock *usk) +{ + atomic_long_inc(&usk->inflight); +} + +static void inc_inflight_move_tail(struct bus_sock *u) +{ + atomic_long_inc(&u->inflight); + /* + * If this still might be part of a cycle, move it to the end + * of the list, so that it's checked even if it was already + * passed over + */ + if (u->gc_maybe_cycle) + list_move_tail(&u->link, &gc_candidates); +} + +static bool gc_in_progress = false; +#define BUS_INFLIGHT_TRIGGER_GC 16000 + +void wait_for_bus_gc(void) +{ + /* + * If number of inflight sockets is insane, + * force a garbage collect right now. + */ + if (bus_tot_inflight > BUS_INFLIGHT_TRIGGER_GC && !gc_in_progress) + bus_gc(); + wait_event(bus_gc_wait, gc_in_progress == false); +} + +/* The external entry point: bus_gc() */ +void bus_gc(void) +{ + struct bus_sock *u; + struct bus_sock *next; + struct sk_buff_head hitlist; + struct list_head cursor; + LIST_HEAD(not_cycle_list); + + spin_lock(&bus_gc_lock); + + /* Avoid a recursive GC. */ + if (gc_in_progress) + goto out; + + gc_in_progress = true; + /* + * First, select candidates for garbage collection. Only + * in-flight sockets are considered, and from those only ones + * which don't have any external reference. + * + * Holding bus_gc_lock will protect these candidates from + * being detached, and hence from gaining an external + * reference. Since there are no possible receivers, all + * buffers currently on the candidates' queues stay there + * during the garbage collection. + * + * We also know that no new candidate can be added onto the + * receive queues. Other, non candidate sockets _can_ be + * added to queue, so we must make sure only to touch + * candidates. + */ + list_for_each_entry_safe(u, next, &gc_inflight_list, link) { + long total_refs; + long inflight_refs; + + total_refs = file_count(u->sk.sk_socket->file); + inflight_refs = atomic_long_read(&u->inflight); + + BUG_ON(inflight_refs < 1); + BUG_ON(total_refs < inflight_refs); + if (total_refs == inflight_refs) { + list_move_tail(&u->link, &gc_candidates); + u->gc_candidate = 1; + u->gc_maybe_cycle = 1; + } + } + + /* + * Now remove all internal in-flight reference to children of + * the candidates. + */ + list_for_each_entry(u, &gc_candidates, link) + scan_children(&u->sk, dec_inflight, NULL); + + /* + * Restore the references for children of all candidates, + * which have remaining references. Do this recursively, so + * only those remain, which form cyclic references. + * + * Use a "cursor" link, to make the list traversal safe, even + * though elements might be moved about. + */ + list_add(&cursor, &gc_candidates); + while (cursor.next != &gc_candidates) { + u = list_entry(cursor.next, struct bus_sock, link); + + /* Move cursor to after the current position. */ + list_move(&cursor, &u->link); + + if (atomic_long_read(&u->inflight) > 0) { + list_move_tail(&u->link, ¬_cycle_list); + u->gc_maybe_cycle = 0; + scan_children(&u->sk, inc_inflight_move_tail, NULL); + } + } + list_del(&cursor); + + /* + * not_cycle_list contains those sockets which do not make up a + * cycle. Restore these to the inflight list. + */ + while (!list_empty(¬_cycle_list)) { + u = list_entry(not_cycle_list.next, struct bus_sock, link); + u->gc_candidate = 0; + list_move_tail(&u->link, &gc_inflight_list); + } + + /* + * Now gc_candidates contains only garbage. Restore original + * inflight counters for these as well, and remove the skbuffs + * which are creating the cycle(s). + */ + skb_queue_head_init(&hitlist); + list_for_each_entry(u, &gc_candidates, link) + scan_children(&u->sk, inc_inflight, &hitlist); + + spin_unlock(&bus_gc_lock); + + /* Here we are. Hitlist is filled. Die. */ + __skb_queue_purge(&hitlist); + + spin_lock(&bus_gc_lock); + + /* All candidates should have been detached by now. */ + BUG_ON(!list_empty(&gc_candidates)); + gc_in_progress = false; + wake_up(&bus_gc_wait); + + out: + spin_unlock(&bus_gc_lock); +} diff --git a/net/bus/nfdbus/Kconfig b/net/bus/nfdbus/Kconfig new file mode 100644 index 000000000000..25699a12e845 --- /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 000000000000..1a825f83f9c9 --- /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/matchrule.c b/net/bus/nfdbus/matchrule.c new file mode 100644 index 000000000000..4106bd5deed2 --- /dev/null +++ b/net/bus/nfdbus/matchrule.c @@ -0,0 +1,1132 @@ +/* + * matchrule.c D-Bus match rule implementation + * + * Based on signals.c from dbus + * + * Copyright (C) 2010 Collabora, Ltd. + * Copyright (C) 2003, 2005 Red Hat, Inc. + * + * 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 + * + */ + +#include "matchrule.h" + +#include <linux/rbtree.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include "message.h" + +enum bus_match_flags { + BUS_MATCH_MESSAGE_TYPE = 1 << 0, + BUS_MATCH_INTERFACE = 1 << 1, + BUS_MATCH_MEMBER = 1 << 2, + BUS_MATCH_SENDER = 1 << 3, + BUS_MATCH_DESTINATION = 1 << 4, + BUS_MATCH_PATH = 1 << 5, + BUS_MATCH_ARGS = 1 << 6, + BUS_MATCH_PATH_NAMESPACE = 1 << 7, + BUS_MATCH_CLIENT_IS_EAVESDROPPING = 1 << 8 +}; + +struct bus_match_rule { + /* For debugging only*/ + char *rule_text; + + unsigned int flags; /**< BusMatchFlags */ + + int message_type; + char *interface; + char *member; + char *sender; + char *destination; + char *path; + + unsigned int *arg_lens; + char **args; + int args_len; + + /* bus_match_rule is attached to rule_pool, either in a simple + * double-linked list if the rule does not have any interface, or in a + * red-black tree sorted by interface. If several rules can have the + * same interface, the first one is attached with struct rb_node and the + * next ones are in the list + */ + + struct rb_node node; + /* Doubly-linked non-circular list. If the rule has an interface, it is + * in the rb tree and the single head is right here. Otherwise, the + * single head is in rule_pool->rules_without_iface. With this data + * structure, we don't need any allocation to insert or remove the rule. + */ + struct hlist_head first; + struct hlist_node list; + + /* used to delete all names from the tree */ + struct list_head del_list; +}; + +struct dbus_name { + struct rb_node node; + char *name; + + /* used to delete all names from the tree */ + struct list_head del_list; +}; + +#define BUS_MATCH_ARG_IS_PATH 0x8000000u + +#define DBUS_STRING_MAX_LENGTH 1024 + +/** Max length of a match rule string; to keep people from hosing the + * daemon with some huge rule + */ +#define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 + +struct bus_match_rule *bus_match_rule_new(gfp_t gfp_flags) +{ + struct bus_match_rule *rule; + + rule = kzalloc(sizeof(struct bus_match_rule), gfp_flags); + if (rule == NULL) + return NULL; + + return rule; +} + +void bus_match_rule_free(struct bus_match_rule *rule) +{ + kfree(rule->rule_text); + kfree(rule->interface); + kfree(rule->member); + kfree(rule->sender); + kfree(rule->destination); + kfree(rule->path); + kfree(rule->arg_lens); + + /* can't use dbus_free_string_array() since there + * are embedded NULL + */ + if (rule->args) { + int i; + + i = 0; + while (i < rule->args_len) { + kfree(rule->args[i]); + ++i; + } + + kfree(rule->args); + } + + kfree(rule); +} + +static int +bus_match_rule_set_message_type(struct bus_match_rule *rule, + int type, + gfp_t gfp_flags) +{ + rule->flags |= BUS_MATCH_MESSAGE_TYPE; + + rule->message_type = type; + + return 1; +} + +static int +bus_match_rule_set_interface(struct bus_match_rule *rule, + const char *interface, + gfp_t gfp_flags) +{ + char *new; + + WARN_ON(!interface); + + new = kstrdup(interface, gfp_flags); + if (new == NULL) + return 0; + + rule->flags |= BUS_MATCH_INTERFACE; + kfree(rule->interface); + rule->interface = new; + + return 1; +} + +static int +bus_match_rule_set_member(struct bus_match_rule *rule, + const char *member, + gfp_t gfp_flags) +{ + char *new; + + WARN_ON(!member); + + new = kstrdup(member, gfp_flags); + if (new == NULL) + return 0; + + rule->flags |= BUS_MATCH_MEMBER; + kfree(rule->member); + rule->member = new; + + return 1; +} + +static int +bus_match_rule_set_sender(struct bus_match_rule *rule, + const char *sender, + gfp_t gfp_flags) +{ + char *new; + + WARN_ON(!sender); + + new = kstrdup(sender, gfp_flags); + if (new == NULL) + return 0; + + rule->flags |= BUS_MATCH_SENDER; + kfree(rule->sender); + rule->sender = new; + + return 1; +} + +static int +bus_match_rule_set_destination(struct bus_match_rule *rule, + const char *destination, + gfp_t gfp_flags) +{ + char *new; + + WARN_ON(!destination); + + new = kstrdup(destination, gfp_flags); + if (new == NULL) + return 0; + + rule->flags |= BUS_MATCH_DESTINATION; + kfree(rule->destination); + rule->destination = new; + + return 1; +} + +#define ISWHITE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || \ + ((c) == '\r')) + +static int find_key(const char *str, int start, char *key, int *value_pos) +{ + const char *p; + const char *s; + const char *key_start; + const char *key_end; + + s = str; + + p = s + start; + + while (*p && ISWHITE(*p)) + ++p; + + key_start = p; + + while (*p && *p != '=' && !ISWHITE(*p)) + ++p; + + key_end = p; + + while (*p && ISWHITE(*p)) + ++p; + + if (key_start == key_end) { + /* Empty match rules or trailing whitespace are OK */ + *value_pos = p - s; + return 1; + } + + if (*p != '=') { + pr_warn("Match rule has a key with no subsequent '=' character"); + return 0; + } + ++p; + + strncat(key, key_start, key_end - key_start); + + *value_pos = p - s; + + return 1; +} + +static int find_value(const char *str, int start, const char *key, char *value, + int *value_end) +{ + const char *p; + const char *s; + char quote_char; + int orig_len; + + orig_len = strlen(value); + + s = str; + + p = s + start; + + quote_char = '\0'; + + while (*p) { + if (quote_char == '\0') { + switch (*p) { + case '\0': + goto done; + + case '\'': + quote_char = '\''; + goto next; + + case ',': + ++p; + goto done; + + case '\\': + quote_char = '\\'; + goto next; + + default: + strncat(value, p, 1); + } + } else if (quote_char == '\\') { + /*\ only counts as an escape if escaping a quote mark */ + if (*p != '\'') + strncat(value, "\\", 1); + + strncat(value, p, 1); + + quote_char = '\0'; + } else { + if (*p == '\'') + quote_char = '\0'; + else + strncat(value, p, 1); + } + +next: + ++p; + } + +done: + + if (quote_char == '\\') + strncat(value, "\\", 1); + else if (quote_char == '\'') { + pr_warn("Unbalanced quotation marks in match rule"); + return 0; + } + + /* Zero-length values are allowed */ + + *value_end = p - s; + + return 1; +} + +/* duplicates aren't allowed so the real legitimate max is only 6 or + * so. Leaving extra so we don't have to bother to update it. + * FIXME this is sort of busted now with arg matching, but we let + * you match on up to 10 args for now + */ +#define MAX_RULE_TOKENS 16 + +/* this is slightly too high level to be termed a "token" + * but let's not be pedantic. + */ +struct rule_token { + char *key; + char *value; +}; + +static int tokenize_rule(const char *rule_text, + struct rule_token tokens[MAX_RULE_TOKENS], + gfp_t gfp_flags) +{ + int i; + int pos; + int retval; + + retval = 0; + + i = 0; + pos = 0; + while (i < MAX_RULE_TOKENS && + pos < strlen(rule_text)) { + char *key; + char *value; + + key = kzalloc(DBUS_STRING_MAX_LENGTH, gfp_flags); + if (!key) { + pr_err("Out of memory"); + return 0; + } + + value = kzalloc(DBUS_STRING_MAX_LENGTH, gfp_flags); + if (!value) { + kfree(key); + pr_err("Out of memory"); + return 0; + } + + if (!find_key(rule_text, pos, key, &pos)) + goto out; + + if (strlen(key) == 0) + goto next; + + tokens[i].key = key; + + if (!find_value(rule_text, pos, tokens[i].key, value, &pos)) + goto out; + + tokens[i].value = value; + +next: + ++i; + } + + retval = 1; + +out: + if (!retval) { + i = 0; + while (tokens[i].key || tokens[i].value) { + kfree(tokens[i].key); + kfree(tokens[i].value); + tokens[i].key = NULL; + tokens[i].value = NULL; + ++i; + } + } + + return retval; +} + +/* + * The format is comma-separated with strings quoted with single quotes + * as for the shell (to escape a literal single quote, use '\''). + * + * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus', + * member='Foo', path='/bar/foo',destination=':452345.34' + * + */ +struct bus_match_rule *bus_match_rule_parse(const char *rule_text, + gfp_t gfp_flags) +{ + struct bus_match_rule *rule; + struct rule_token tokens[MAX_RULE_TOKENS+1]; /* NULL termination + 1 */ + int i; + + if (strlen(rule_text) > DBUS_MAXIMUM_MATCH_RULE_LENGTH) { + pr_warn("Match rule text is %ld bytes, maximum is %d", + strlen(rule_text), + DBUS_MAXIMUM_MATCH_RULE_LENGTH); + return NULL; + } + + memset(tokens, '\0', sizeof(tokens)); + + rule = bus_match_rule_new(gfp_flags); + if (rule == NULL) { + pr_err("Out of memory"); + goto failed; + } + + rule->rule_text = kstrdup(rule_text, gfp_flags); + if (rule->rule_text == NULL) { + pr_err("Out of memory"); + goto failed; + } + + if (!tokenize_rule(rule_text, tokens, gfp_flags)) + goto failed; + + i = 0; + while (tokens[i].key != NULL) { + const char *key = tokens[i].key; + const char *value = tokens[i].value; + + if (strcmp(key, "type") == 0) { + int t; + + if (rule->flags & BUS_MATCH_MESSAGE_TYPE) { + pr_warn("Key %s specified twice in match rule\n", + key); + goto failed; + } + + t = dbus_message_type_from_string(value); + + if (t == DBUS_MESSAGE_TYPE_INVALID) { + pr_warn("Invalid message type (%s) in match rule\n", + value); + goto failed; + } + + if (!bus_match_rule_set_message_type(rule, t, + gfp_flags)) { + pr_err("Out of memeory"); + goto failed; + } + } else if (strcmp(key, "sender") == 0) { + if (rule->flags & BUS_MATCH_SENDER) { + pr_warn("Key %s specified twice in match rule\n", + key); + goto failed; + } + + if (!bus_match_rule_set_sender(rule, value, + gfp_flags)) { + pr_err("Out of memeory"); + goto failed; + } + } else if (strcmp(key, "interface") == 0) { + if (rule->flags & BUS_MATCH_INTERFACE) { + pr_warn("Key %s specified twice in match rule\n", + key); + goto failed; + } + + if (!bus_match_rule_set_interface(rule, value, + gfp_flags)) { + pr_err("Out of memeory"); + goto failed; + } + } else if (strcmp(key, "member") == 0) { + if (rule->flags & BUS_MATCH_MEMBER) { + pr_warn("Key %s specified twice in match rule\n", + key); + goto failed; + } + + if (!bus_match_rule_set_member(rule, value, + gfp_flags)) { + pr_err("Out of memeory"); + goto failed; + } + } else if (strcmp(key, "destination") == 0) { + if (rule->flags & BUS_MATCH_DESTINATION) { + pr_warn("Key %s specified twice in match rule\n", + key); + goto failed; + } + + if (!bus_match_rule_set_destination(rule, value, + gfp_flags)) { + pr_err("Out of memeory"); + goto failed; + } + } else if (strcmp(key, "eavesdrop") == 0) { + if (strcmp(value, "true") == 0) { + rule->flags |= BUS_MATCH_CLIENT_IS_EAVESDROPPING; + } else if (strcmp(value, "false") == 0) { + rule->flags &= ~(BUS_MATCH_CLIENT_IS_EAVESDROPPING); + } else { + pr_warn("eavesdrop='%s' is invalid, " \ + "it should be 'true' or 'false'\n", + value); + goto failed; + } + } else if (strncmp(key, "arg", 3) != 0) { + pr_warn("Unknown key \"%s\" in match rule\n", + key); + goto failed; + } + + ++i; + } + + goto out; + +failed: + if (rule) { + bus_match_rule_free(rule); + rule = NULL; + } + +out: + + i = 0; + while (tokens[i].key || tokens[i].value) { + WARN_ON(i >= MAX_RULE_TOKENS); + kfree(tokens[i].key); + kfree(tokens[i].value); + ++i; + } + + return rule; +} + +/* return the match rule containing the hlist_head. It may not be the first + * match rule in the list. */ +struct bus_match_rule *match_rule_search(struct rb_root *root, + const char *interface) +{ + struct rb_node *node = root->rb_node; + + while (node) { + struct bus_match_rule *data = + container_of(node, struct bus_match_rule, node); + int result; + + result = strcmp(interface, data->interface); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return data; + } + return NULL; +} + +void match_rule_insert(struct rb_root *root, struct bus_match_rule *data) +{ + struct rb_node **new = &(root->rb_node), *parent = NULL; + + /* Figure out where to put new node */ + while (*new) { + struct bus_match_rule *this = + container_of(*new, struct bus_match_rule, node); + int result = strcmp(data->interface, this->interface); + + parent = *new; + if (result < 0) + new = &((*new)->rb_left); + else if (result > 0) + new = &((*new)->rb_right); + else { + /* the head is not used */ + INIT_HLIST_HEAD(&data->first); + /* Add it at the beginning of the list */ + hlist_add_head(&data->list, &this->first); + return; + } + } + + /* this rule is single in its list */ + INIT_HLIST_HEAD(&data->first); + hlist_add_head(&data->list, &data->first); + + /* Add new node and rebalance tree. */ + rb_link_node(&data->node, parent, new); + rb_insert_color(&data->node, root); +} + +struct bus_match_maker *bus_matchmaker_new(gfp_t gfp_flags) +{ + struct bus_match_maker *matchmaker; + int i; + + matchmaker = kzalloc(sizeof(struct bus_match_maker), gfp_flags); + if (matchmaker == NULL) + return NULL; + + for (i = DBUS_MESSAGE_TYPE_INVALID; i < DBUS_NUM_MESSAGE_TYPES; i++) { + struct rule_pool *p = matchmaker->rules_by_type + i; + + p->rules_by_iface = RB_ROOT; + } + + kref_init(&matchmaker->kref); + + return matchmaker; +} + +void bus_matchmaker_free(struct kref *kref) +{ + struct bus_match_maker *matchmaker; + struct list_head del_list; + struct rb_node *n; + int i; + + matchmaker = container_of(kref, struct bus_match_maker, kref); + + /* free names */ + INIT_LIST_HEAD(&del_list); + n = matchmaker->names.rb_node; + if (n) { + struct dbus_name *dbus_name, *cur, *tmp; + + dbus_name = rb_entry(n, struct dbus_name, node); + list_add_tail(&dbus_name->del_list, &del_list); + + list_for_each_entry(cur, &del_list, del_list) { + struct dbus_name *right, *left; + if (cur->node.rb_right) { + right = rb_entry(cur->node.rb_right, + struct dbus_name, node); + list_add_tail(&right->del_list, &del_list); + } + if (cur->node.rb_left) { + left = rb_entry(cur->node.rb_left, + struct dbus_name, node); + list_add_tail(&left->del_list, &del_list); + } + } + list_for_each_entry_safe(dbus_name, tmp, &del_list, del_list) { + kfree(dbus_name->name); + list_del(&dbus_name->del_list); + kfree(dbus_name); + } + } + WARN_ON(!list_empty_careful(&del_list)); + + /* free match rules */ + for (i = 0 ; i < DBUS_NUM_MESSAGE_TYPES ; i++) { + struct rule_pool *pool = matchmaker->rules_by_type + i; + struct bus_match_rule *match_rule, *cur, *tmp; + struct hlist_node *list_tmp, *list_tmp2; + + /* free match rules from the list */ + hlist_for_each_entry_safe(cur, list_tmp, list_tmp2, + &pool->rules_without_iface, list) { + bus_match_rule_free(cur); + } + + /* free match rules from the tree */ + if (!pool->rules_by_iface.rb_node) + continue; + match_rule = rb_entry(pool->rules_by_iface.rb_node, + struct bus_match_rule, node); + list_add_tail(&match_rule->del_list, &del_list); + + list_for_each_entry(cur, &del_list, del_list) { + struct bus_match_rule *right, *left; + if (cur->node.rb_right) { + right = rb_entry(cur->node.rb_right, + struct bus_match_rule, node); + list_add_tail(&right->del_list, &del_list); + } + if (cur->node.rb_left) { + left = rb_entry(cur->node.rb_left, + struct bus_match_rule, node); + list_add_tail(&left->del_list, &del_list); + } + } + list_for_each_entry_safe(match_rule, tmp, &del_list, del_list) { + /* keep a ref during the loop to ensure the first + * iteration of the loop does not delete it */ + hlist_for_each_entry_safe(cur, list_tmp, list_tmp2, + &match_rule->first, list) { + if (cur != match_rule) + bus_match_rule_free(cur); + } + list_del(&match_rule->del_list); + bus_match_rule_free(match_rule); + } + WARN_ON(!list_empty_careful(&del_list)); + } + + kfree(matchmaker); +} + +/* The rule can't be modified after it's added. */ +int bus_matchmaker_add_rule(struct bus_match_maker *matchmaker, + struct bus_match_rule *rule) +{ + struct rule_pool *pool; + + WARN_ON(rule->message_type < 0); + WARN_ON(rule->message_type >= DBUS_NUM_MESSAGE_TYPES); + + pool = matchmaker->rules_by_type + rule->message_type; + + if (rule->interface) + match_rule_insert(&pool->rules_by_iface, rule); + else + hlist_add_head(&rule->list, &pool->rules_without_iface); + + return 1; +} + +static int match_rule_equal(struct bus_match_rule *a, + struct bus_match_rule *b) +{ + if (a->flags != b->flags) + return 0; + + if ((a->flags & BUS_MATCH_MESSAGE_TYPE) && + a->message_type != b->message_type) + return 0; + + if ((a->flags & BUS_MATCH_MEMBER) && + strcmp(a->member, b->member) != 0) + return 0; + + if ((a->flags & BUS_MATCH_PATH) && + strcmp(a->path, b->path) != 0) + return 0; + + if ((a->flags & BUS_MATCH_INTERFACE) && + strcmp(a->interface, b->interface) != 0) + return 0; + + if ((a->flags & BUS_MATCH_SENDER) && + strcmp(a->sender, b->sender) != 0) + return 0; + + if ((a->flags & BUS_MATCH_DESTINATION) && + strcmp(a->destination, b->destination) != 0) + return 0; + + if (a->flags & BUS_MATCH_ARGS) { + int i; + + if (a->args_len != b->args_len) + return 0; + + i = 0; + while (i < a->args_len) { + int length; + + if ((a->args[i] != NULL) != (b->args[i] != NULL)) + return 0; + + if (a->arg_lens[i] != b->arg_lens[i]) + return 0; + + length = a->arg_lens[i] & ~BUS_MATCH_ARG_IS_PATH; + + if (a->args[i] != NULL) { + WARN_ON(!b->args[i]); + if (memcmp(a->args[i], b->args[i], length) != 0) + return 0; + } + + ++i; + } + } + + return 1; +} + +/* Remove a single rule which is equal to the given rule by value */ +void bus_matchmaker_remove_rule_by_value(struct bus_match_maker *matchmaker, + struct bus_match_rule *rule) +{ + struct rule_pool *pool; + + WARN_ON(rule->message_type < 0); + WARN_ON(rule->message_type >= DBUS_NUM_MESSAGE_TYPES); + + pool = matchmaker->rules_by_type + rule->message_type; + + if (rule->interface) { + struct bus_match_rule *head = + match_rule_search(&pool->rules_by_iface, + rule->interface); + + struct hlist_node *cur; + struct bus_match_rule *cur_rule; + hlist_for_each_entry(cur_rule, cur, &head->first, list) { + if (match_rule_equal(cur_rule, rule)) { + hlist_del(cur); + if (hlist_empty(&head->first)) + rb_erase(&head->node, + &pool->rules_by_iface); + bus_match_rule_free(cur_rule); + break; + } + } + } else { + struct hlist_head *head = &pool->rules_without_iface; + + struct hlist_node *cur; + struct bus_match_rule *cur_rule; + hlist_for_each_entry(cur_rule, cur, head, list) { + if (match_rule_equal(cur_rule, rule)) { + hlist_del(cur); + bus_match_rule_free(cur_rule); + break; + } + } + } + +} + +static int connection_is_primary_owner(struct bus_match_maker *connection, + const char *service_name) +{ + struct rb_node *node = connection->names.rb_node; + + if (!service_name) + return 0; + + while (node) { + struct dbus_name *data = container_of(node, struct dbus_name, + node); + int result; + + result = strcmp(service_name, data->name); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return 1; + } + return 0; +} + +static int match_rule_matches(struct bus_match_maker *matchmaker, + struct bus_match_maker *sender, + int eavesdrop, + struct bus_match_rule *rule, + const struct dbus_message *message) +{ + /* Don't consider the rule if this is a eavesdropping match rule + * and eavesdropping is not allowed on that peer */ + if ((rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) && !eavesdrop) + return 0; + + /* Since D-Bus 1.5.6, match rules do not match messages which have a + * DESTINATION field unless the match rule specifically requests this + * by specifying eavesdrop='true' in the match rule. */ + if (message->destination && + !(rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING)) + return 0; + + if (rule->flags & BUS_MATCH_MEMBER) { + const char *member; + + WARN_ON(!rule->member); + + member = message->member; + if (member == NULL) + return 0; + + if (strcmp(member, rule->member) != 0) + return 0; + } + + if (rule->flags & BUS_MATCH_SENDER) { + WARN_ON(!rule->sender); + + if (sender == NULL) { + if (strcmp(rule->sender, + "org.freedesktop.DBus") != 0) + return 0; + } else + if (!connection_is_primary_owner(sender, rule->sender)) + return 0; + } + + if (rule->flags & BUS_MATCH_DESTINATION) { + const char *destination; + + WARN_ON(!rule->destination); + + destination = message->destination; + if (destination == NULL) + return 0; + + /* This will not just work out of the box because it this is + * an eavesdropping match rule. */ + if (matchmaker == NULL) { + if (strcmp(rule->destination, + "org.freedesktop.DBus") != 0) + return 0; + } else + if (!connection_is_primary_owner(matchmaker, + rule->destination)) + return 0; + } + + if (rule->flags & BUS_MATCH_PATH) { + const char *path; + + WARN_ON(!rule->path); + + path = message->path; + if (path == NULL) + return 0; + + if (strcmp(path, rule->path) != 0) + return 0; + } + + return 1; +} + +static bool get_recipients_from_list(struct bus_match_maker *matchmaker, + struct bus_match_maker *sender, + int eavesdrop, + struct hlist_head *rules, + const struct dbus_message *message) +{ + struct hlist_node *cur; + struct bus_match_rule *rule; + + if (rules == NULL) { + pr_debug("no rules of this type\n"); + return 0; + } + + hlist_for_each_entry(rule, cur, rules, list) { + if (match_rule_matches(matchmaker, sender, eavesdrop, rule, + message)) { + pr_debug("[YES] deliver with match rule \"%s\"\n", + rule->rule_text); + return 1; + } else { + pr_debug("[NO] deliver with match rule \"%s\"\n", + rule->rule_text); + } + } + pr_debug("[NO] no match rules\n"); + return 0; +} + +static struct hlist_head +*bus_matchmaker_get_rules(struct bus_match_maker *matchmaker, + int message_type, const char *interface) +{ + static struct hlist_head empty = {0,}; + struct rule_pool *p; + + WARN_ON(message_type < 0); + WARN_ON(message_type >= DBUS_NUM_MESSAGE_TYPES); + + p = matchmaker->rules_by_type + message_type; + + if (interface == NULL) + return &p->rules_without_iface; + else { + struct bus_match_rule *rule = + match_rule_search(&p->rules_by_iface, interface); + if (rule) + return &rule->first; + else + return ∅ + } +} + +bool bus_matchmaker_filter(struct bus_match_maker *matchmaker, + struct bus_match_maker *sender, + int eavesdrop, + const struct dbus_message *message) +{ + int type; + const char *interface; + struct hlist_head *neither, *just_type, *just_iface, *both; + + type = message->type; + interface = message->interface; + + neither = bus_matchmaker_get_rules(matchmaker, + DBUS_MESSAGE_TYPE_INVALID, NULL); + just_type = just_iface = both = NULL; + + if (interface != NULL) + just_iface = bus_matchmaker_get_rules(matchmaker, + DBUS_MESSAGE_TYPE_INVALID, + interface); + + if (type > DBUS_MESSAGE_TYPE_INVALID && type < DBUS_NUM_MESSAGE_TYPES) { + just_type = bus_matchmaker_get_rules(matchmaker, type, NULL); + + if (interface != NULL) + both = bus_matchmaker_get_rules(matchmaker, type, + interface); + } + + if (get_recipients_from_list(matchmaker, sender, eavesdrop, neither, + message)) + return 1; + if (get_recipients_from_list(matchmaker, sender, eavesdrop, just_iface, + message)) + return 1; + if (get_recipients_from_list(matchmaker, sender, eavesdrop, just_type, + message)) + return 1; + if (get_recipients_from_list(matchmaker, sender, eavesdrop, both, + message)) + return 1; + + return connection_is_primary_owner(matchmaker, message->destination); +} + +void bus_matchmaker_add_name(struct bus_match_maker *matchmaker, + const char *name, + gfp_t gfp_flags) +{ + struct dbus_name *dbus_name; + struct rb_node **new = &(matchmaker->names.rb_node), *parent = NULL; + + dbus_name = kmalloc(sizeof(struct dbus_name), gfp_flags); + if (!dbus_name) + return; + dbus_name->name = kstrdup(name, gfp_flags); + if (!dbus_name->name) + return; + + /* Figure out where to put new node */ + while (*new) { + struct dbus_name *this = container_of(*new, struct dbus_name, + node); + int result = strcmp(dbus_name->name, this->name); + + parent = *new; + if (result < 0) + new = &((*new)->rb_left); + else if (result > 0) + new = &((*new)->rb_right); + else + return; + } + + /* Add new node and rebalance tree. */ + rb_link_node(&dbus_name->node, parent, new); + rb_insert_color(&dbus_name->node, &matchmaker->names); +} + +void bus_matchmaker_remove_name(struct bus_match_maker *matchmaker, + const char *name) +{ + struct rb_node *node = matchmaker->names.rb_node; + + while (node) { + struct dbus_name *data = container_of(node, struct dbus_name, + node); + int result; + + result = strcmp(name, data->name); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else { + rb_erase(&data->node, &matchmaker->names); + kfree(data->name); + kfree(data); + } + } + +} + diff --git a/net/bus/nfdbus/matchrule.h b/net/bus/nfdbus/matchrule.h new file mode 100644 index 000000000000..e16580c9b7d2 --- /dev/null +++ b/net/bus/nfdbus/matchrule.h @@ -0,0 +1,82 @@ +/* + * signals.h Bus signal connection implementation + * + * Copyright (C) 2003 Red Hat, Inc. + * + * 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 BUS_SIGNALS_H +#define BUS_SIGNALS_H + +#include <linux/gfp.h> +#include <linux/list.h> +#include <linux/rbtree.h> +#include <linux/slab.h> +#include <net/af_bus.h> + +#include "message.h" + +struct bus_match_rule *bus_match_rule_new(gfp_t gfp_flags); +void bus_match_rule_free(struct bus_match_rule *rule); + +struct bus_match_rule *bus_match_rule_parse(const char *rule_text, + gfp_t gfp_flags); + +struct rule_pool { + /* Maps non-NULL interface names to a list of bus_match_rule */ + struct rb_root rules_by_iface; + + /* List of bus_match_rule which don't specify an interface */ + struct hlist_head rules_without_iface; +}; + +struct bus_match_maker { + struct sockaddr_bus addr; + + struct hlist_node table_node; + + /* Pools of rules, grouped by the type of message they match. 0 + * (DBUS_MESSAGE_TYPE_INVALID) represents rules that do not specify a + * message type. + */ + struct rule_pool rules_by_type[DBUS_NUM_MESSAGE_TYPES]; + + struct rb_root names; + + struct kref kref; +}; + + +struct bus_match_maker *bus_matchmaker_new(gfp_t gfp_flags); +void bus_matchmaker_free(struct kref *kref); + +int bus_matchmaker_add_rule(struct bus_match_maker *matchmaker, + struct bus_match_rule *rule); +void bus_matchmaker_remove_rule_by_value(struct bus_match_maker *matchmaker, + struct bus_match_rule *value); + +bool bus_matchmaker_filter(struct bus_match_maker *matchmaker, + struct bus_match_maker *sender, + int eavesdrop, + const struct dbus_message *message); + +void bus_matchmaker_add_name(struct bus_match_maker *matchmaker, + const char *name, gfp_t gfp_flags); +void bus_matchmaker_remove_name(struct bus_match_maker *matchmaker, + const char *name); + +#endif /* BUS_SIGNALS_H */ diff --git a/net/bus/nfdbus/message.c b/net/bus/nfdbus/message.c new file mode 100644 index 000000000000..93c409c6f16c --- /dev/null +++ b/net/bus/nfdbus/message.c @@ -0,0 +1,194 @@ +/* + * message.c Basic D-Bus message parsing + * + * Copyright (C) 2010-2012 Collabora Ltd + * Authors: Alban Crequy <alban.crequy@collabora.co.uk> + * Copyright (C) 2002, 2003, 2004, 2005 Red Hat Inc. + * Copyright (C) 2002, 2003 CodeFactory AB + * + * 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 + * + */ + +#include <linux/slab.h> + +#include "message.h" + +int dbus_message_type_from_string(const char *type_str) +{ + if (strcmp(type_str, "method_call") == 0) + return DBUS_MESSAGE_TYPE_METHOD_CALL; + if (strcmp(type_str, "method_return") == 0) + return DBUS_MESSAGE_TYPE_METHOD_RETURN; + else if (strcmp(type_str, "signal") == 0) + return DBUS_MESSAGE_TYPE_SIGNAL; + else if (strcmp(type_str, "error") == 0) + return DBUS_MESSAGE_TYPE_ERROR; + else + return DBUS_MESSAGE_TYPE_INVALID; +} + +int dbus_message_parse(unsigned char *message, size_t len, + struct dbus_message *dbus_message) +{ + unsigned char *cur; + int array_header_len; + + dbus_message->message = message; + + if (len < 4 + 4 + 4 + 4 || message[1] == 0 || message[1] > 4) + return -EINVAL; + + dbus_message->type = message[1]; + dbus_message->body_length = *((u32 *)(message + 4)); + cur = message + 12; + array_header_len = *(u32 *)cur; + dbus_message->len_offset = 12; + cur += 4; + while (cur < message + len + && cur < message + 12 + 4 + array_header_len) { + int header_code; + int signature_len; + unsigned char *signature; + int str_len; + unsigned char *str; + + /* D-Bus alignment craziness */ + if ((cur - message) % 8 != 0) + cur += 8 - (cur - message) % 8; + + header_code = *(char *)cur; + cur++; + signature_len = *(char *)cur; + /* All header fields of the current D-Bus spec have a simple + * type, either o, s, g, or u */ + if (signature_len != 1) + return -EINVAL; + cur++; + signature = cur; + cur += signature_len + 1; + if (signature[0] != 'o' && + signature[0] != 's' && + signature[0] != 'g' && + signature[0] != 'u') + return -EINVAL; + + if (signature[0] == 'u') { + cur += 4; + continue; + } + + if (signature[0] != 'g') { + str_len = *(u32 *)cur; + cur += 4; + } else { + str_len = *(char *)cur; + cur += 1; + } + + str = cur; + switch (header_code) { + case 1: + dbus_message->path = str; + break; + case 2: + dbus_message->interface = str; + break; + case 3: + dbus_message->member = str; + break; + case 6: + dbus_message->destination = str; + break; + case 7: + dbus_message->sender = str; + break; + case 8: + dbus_message->body_signature = str; + break; + } + cur += str_len + 1; + } + + dbus_message->padding_end = (8 - (cur - message) % 8) % 8; + + /* Jump to body D-Bus alignment craziness */ + if ((cur - message) % 8 != 0) + cur += 8 - (cur - message) % 8; + dbus_message->new_header_offset = cur - message; + + if (dbus_message->new_header_offset + + dbus_message->body_length != len) { + pr_warn("Message truncated? " \ + "Header %d + Body %d != Length %zd\n", + dbus_message->new_header_offset, + dbus_message->body_length, len); + return -EINVAL; + } + + if (dbus_message->body_signature && + dbus_message->body_signature[0] == 's') { + int str_len; + str_len = *(u32 *)cur; + cur += 4; + dbus_message->arg0 = cur; + cur += str_len + 1; + } + + if ((cur - message) % 4 != 0) + cur += 4 - (cur - message) % 4; + + if (dbus_message->body_signature && + dbus_message->body_signature[0] == 's' && + dbus_message->body_signature[1] == 's') { + int str_len; + str_len = *(u32 *)cur; + cur += 4; + dbus_message->arg1 = cur; + cur += str_len + 1; + } + + if ((cur - message) % 4 != 0) + cur += 4 - (cur - message) % 4; + + if (dbus_message->body_signature && + dbus_message->body_signature[0] == 's' && + dbus_message->body_signature[1] == 's' && + dbus_message->body_signature[2] == 's') { + int str_len; + str_len = *(u32 *)cur; + cur += 4; + dbus_message->arg2 = cur; + cur += str_len + 1; + } + + if ((cur - message) % 4 != 0) + cur += 4 - (cur - message) % 4; + + if (dbus_message->type == DBUS_MESSAGE_TYPE_SIGNAL && + dbus_message->sender && dbus_message->path && + dbus_message->interface && dbus_message->member && + dbus_message->arg0 && + strcmp(dbus_message->sender, "org.freedesktop.DBus") == 0 && + strcmp(dbus_message->interface, "org.freedesktop.DBus") == 0 && + strcmp(dbus_message->path, "/org/freedesktop/DBus") == 0) { + if (strcmp(dbus_message->member, "NameAcquired") == 0) + dbus_message->name_acquired = dbus_message->arg0; + else if (strcmp(dbus_message->member, "NameLost") == 0) + dbus_message->name_lost = dbus_message->arg0; + } + + return 0; +} diff --git a/net/bus/nfdbus/message.h b/net/bus/nfdbus/message.h new file mode 100644 index 000000000000..e3ea4d3253b6 --- /dev/null +++ b/net/bus/nfdbus/message.h @@ -0,0 +1,71 @@ +/* + * message.h Basic D-Bus message parsing + * + * Copyright (C) 2010 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 DBUS_MESSAGE_H +#define DBUS_MESSAGE_H + +#include <linux/list.h> + +#define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 + +/* Types of message */ + +#define DBUS_MESSAGE_TYPE_INVALID 0 +#define DBUS_MESSAGE_TYPE_METHOD_CALL 1 +#define DBUS_MESSAGE_TYPE_METHOD_RETURN 2 +#define DBUS_MESSAGE_TYPE_ERROR 3 +#define DBUS_MESSAGE_TYPE_SIGNAL 4 +#define DBUS_NUM_MESSAGE_TYPES 5 + +/* No need to implement a feature-complete parser. It only implement what is + * needed by the bus. */ +struct dbus_message { + char *message; + size_t len; + size_t new_len; + + /* direct pointers to the fields */ + int type; + char *path; + char *interface; + char *member; + char *destination; + char *sender; + char *body_signature; + int body_length; + char *arg0; + char *arg1; + char *arg2; + char *name_acquired; + char *name_lost; + + /* How to add the 'sender' field in the headers */ + int new_header_offset; + int len_offset; + int padding_end; +}; + +int dbus_message_type_from_string(const char *type_str); + +int dbus_message_parse(unsigned char *message, size_t len, + struct dbus_message *dbus_message); + +#endif /* DBUS_MESSAGE_H */ diff --git a/net/bus/nfdbus/nfdbus.c b/net/bus/nfdbus/nfdbus.c new file mode 100644 index 000000000000..22a33e8a44eb --- /dev/null +++ b/net/bus/nfdbus/nfdbus.c @@ -0,0 +1,456 @@ +/* + * 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->portid, 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); + 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->portid, 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->portid, 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 000000000000..477bde32a60f --- /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 */ diff --git a/net/core/scm.c b/net/core/scm.c index 040cebeed45b..d34cfc41ed3a 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -142,7 +142,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) switch (cmsg->cmsg_type) { case SCM_RIGHTS: - if (!sock->ops || sock->ops->family != PF_UNIX) + if (!sock->ops || (sock->ops->family != PF_UNIX && + sock->ops->family != PF_BUS)) goto error; err=scm_fp_copy(cmsg, &p->fp); if (err<0) diff --git a/net/core/sock.c b/net/core/sock.c index a6000fbad294..29ad1f8132b3 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -208,7 +208,7 @@ static const char *const af_family_key_strings[AF_MAX+1] = { "sk_lock-AF_TIPC" , "sk_lock-AF_BLUETOOTH", "sk_lock-IUCV" , "sk_lock-AF_RXRPC" , "sk_lock-AF_ISDN" , "sk_lock-AF_PHONET" , "sk_lock-AF_IEEE802154", "sk_lock-AF_CAIF" , "sk_lock-AF_ALG" , - "sk_lock-AF_NFC" , "sk_lock-AF_MAX" + "sk_lock-AF_NFC" , "sk_lock-AF_BUS" , "sk_lock-AF_MAX" }; static const char *const af_family_slock_key_strings[AF_MAX+1] = { "slock-AF_UNSPEC", "slock-AF_UNIX" , "slock-AF_INET" , @@ -224,7 +224,7 @@ static const char *const af_family_slock_key_strings[AF_MAX+1] = { "slock-AF_TIPC" , "slock-AF_BLUETOOTH", "slock-AF_IUCV" , "slock-AF_RXRPC" , "slock-AF_ISDN" , "slock-AF_PHONET" , "slock-AF_IEEE802154", "slock-AF_CAIF" , "slock-AF_ALG" , - "slock-AF_NFC" , "slock-AF_MAX" + "slock-AF_NFC" , "slock-AF_BUS" , "slock-AF_MAX" }; static const char *const af_family_clock_key_strings[AF_MAX+1] = { "clock-AF_UNSPEC", "clock-AF_UNIX" , "clock-AF_INET" , @@ -240,7 +240,7 @@ static const char *const af_family_clock_key_strings[AF_MAX+1] = { "clock-AF_TIPC" , "clock-AF_BLUETOOTH", "clock-AF_IUCV" , "clock-AF_RXRPC" , "clock-AF_ISDN" , "clock-AF_PHONET" , "clock-AF_IEEE802154", "clock-AF_CAIF" , "clock-AF_ALG" , - "clock-AF_NFC" , "clock-AF_MAX" + "clock-AF_NFC" , "clock-AF_BUS" , "clock-AF_MAX" }; /* diff --git a/security/capability.c b/security/capability.c index 61095df8b89a..ea57f2b664ba 100644 --- a/security/capability.c +++ b/security/capability.c @@ -563,6 +563,12 @@ static int cap_unix_may_send(struct socket *sock, struct socket *other) return 0; } +static int cap_bus_connect(struct sock *sock, struct sock *other, + struct sock *newsk) +{ + return 0; +} + static int cap_socket_create(int family, int type, int protocol, int kern) { return 0; @@ -1016,6 +1022,7 @@ void __init security_fixup_ops(struct security_operations *ops) #ifdef CONFIG_SECURITY_NETWORK set_to_cap_if_null(ops, unix_stream_connect); set_to_cap_if_null(ops, unix_may_send); + set_to_cap_if_null(ops, bus_connect); set_to_cap_if_null(ops, socket_create); set_to_cap_if_null(ops, socket_post_create); set_to_cap_if_null(ops, socket_bind); diff --git a/security/security.c b/security/security.c index 860aeb349cb3..efacf0292b51 100644 --- a/security/security.c +++ b/security/security.c @@ -1060,6 +1060,13 @@ int security_unix_may_send(struct socket *sock, struct socket *other) } EXPORT_SYMBOL(security_unix_may_send); +int security_bus_connect(struct sock *sock, struct sock *other, + struct sock *newsk) +{ + return security_ops->bus_connect(sock, other, newsk); +} +EXPORT_SYMBOL(security_bus_connect); + int security_socket_create(int family, int type, int protocol, int kern) { return security_ops->socket_create(family, type, protocol, kern); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6c77f63c7591..c5b0bbc6137b 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -67,6 +67,7 @@ #include <linux/quota.h> #include <linux/un.h> /* for Unix socket types */ #include <net/af_unix.h> /* for Unix socket types */ +#include <net/af_bus.h> /* for Bus socket types */ #include <linux/parser.h> #include <linux/nfs_mount.h> #include <net/ipv6.h> @@ -4106,6 +4107,39 @@ static int selinux_socket_unix_may_send(struct socket *sock, &ad); } +static int selinux_socket_bus_connect(struct sock *sock, struct sock *other, + struct sock *newsk) +{ + struct sk_security_struct *sksec_sock = sock->sk_security; + struct sk_security_struct *sksec_other = other->sk_security; + struct sk_security_struct *sksec_new = newsk->sk_security; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + int err; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = other; + + err = avc_has_perm(sksec_sock->sid, sksec_other->sid, + sksec_other->sclass, + UNIX_STREAM_SOCKET__CONNECTTO, &ad); + if (err) + return err; + + /* server child socket */ + sksec_new->peer_sid = sksec_sock->sid; + err = security_sid_mls_copy(sksec_other->sid, sksec_sock->sid, + &sksec_new->sid); + if (err) + return err; + + /* connecting socket */ + sksec_sock->peer_sid = sksec_new->sid; + + return 0; +} + static int selinux_inet_sys_rcv_skb(int ifindex, char *addrp, u16 family, u32 peer_sid, struct common_audit_data *ad) @@ -5648,6 +5682,7 @@ static struct security_operations selinux_ops = { .unix_stream_connect = selinux_socket_unix_stream_connect, .unix_may_send = selinux_socket_unix_may_send, + .bus_connect = selinux_socket_bus_connect, .socket_create = selinux_socket_create, .socket_post_create = selinux_socket_post_create, |