]>
Commit | Line | Data |
---|---|---|
5b497af4 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
27cc2a6e JM |
2 | /* |
3 | * Network port table | |
4 | * | |
5 | * SELinux must keep a mapping of network ports to labels/SIDs. This | |
6 | * mapping is maintained as part of the normal policy but a fast cache is | |
7 | * needed to reduce the lookup overhead. | |
8 | * | |
82c21bfa | 9 | * Author: Paul Moore <paul@paul-moore.com> |
27cc2a6e JM |
10 | * |
11 | * This code is heavily based on the "netif" concept originally developed by | |
12 | * James Morris <jmorris@redhat.com> | |
13 | * (see security/selinux/netif.c for more information) | |
27cc2a6e JM |
14 | */ |
15 | ||
16 | /* | |
17 | * (c) Copyright Hewlett-Packard Development Company, L.P., 2008 | |
27cc2a6e JM |
18 | */ |
19 | ||
20 | #include <linux/types.h> | |
21 | #include <linux/rcupdate.h> | |
22 | #include <linux/list.h> | |
5a0e3ad6 | 23 | #include <linux/slab.h> |
27cc2a6e JM |
24 | #include <linux/spinlock.h> |
25 | #include <linux/in.h> | |
26 | #include <linux/in6.h> | |
27 | #include <linux/ip.h> | |
28 | #include <linux/ipv6.h> | |
29 | #include <net/ip.h> | |
30 | #include <net/ipv6.h> | |
27cc2a6e JM |
31 | |
32 | #include "netport.h" | |
33 | #include "objsec.h" | |
34 | ||
35 | #define SEL_NETPORT_HASH_SIZE 256 | |
36 | #define SEL_NETPORT_HASH_BKT_LIMIT 16 | |
37 | ||
38 | struct sel_netport_bkt { | |
39 | int size; | |
40 | struct list_head list; | |
41 | }; | |
42 | ||
43 | struct sel_netport { | |
44 | struct netport_security_struct psec; | |
45 | ||
46 | struct list_head list; | |
47 | struct rcu_head rcu; | |
48 | }; | |
49 | ||
50 | /* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason | |
51 | * for this is that I suspect most users will not make heavy use of both | |
52 | * address families at the same time so one table will usually end up wasted, | |
53 | * if this becomes a problem we can always add a hash table for each address | |
54 | * family later */ | |
55 | ||
56 | static LIST_HEAD(sel_netport_list); | |
57 | static DEFINE_SPINLOCK(sel_netport_lock); | |
58 | static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; | |
59 | ||
27cc2a6e JM |
60 | /** |
61 | * sel_netport_hashfn - Hashing function for the port table | |
62 | * @pnum: port number | |
63 | * | |
64 | * Description: | |
65 | * This is the hashing function for the port table, it returns the bucket | |
66 | * number for the given port. | |
67 | * | |
68 | */ | |
69 | static unsigned int sel_netport_hashfn(u16 pnum) | |
70 | { | |
71 | return (pnum & (SEL_NETPORT_HASH_SIZE - 1)); | |
72 | } | |
73 | ||
74 | /** | |
75 | * sel_netport_find - Search for a port record | |
76 | * @protocol: protocol | |
77 | * @port: pnum | |
78 | * | |
79 | * Description: | |
80 | * Search the network port table and return the matching record. If an entry | |
81 | * can not be found in the table return NULL. | |
82 | * | |
83 | */ | |
84 | static struct sel_netport *sel_netport_find(u8 protocol, u16 pnum) | |
85 | { | |
86 | unsigned int idx; | |
87 | struct sel_netport *port; | |
88 | ||
89 | idx = sel_netport_hashfn(pnum); | |
90 | list_for_each_entry_rcu(port, &sel_netport_hash[idx].list, list) | |
c9b7b979 | 91 | if (port->psec.port == pnum && port->psec.protocol == protocol) |
27cc2a6e JM |
92 | return port; |
93 | ||
94 | return NULL; | |
95 | } | |
96 | ||
97 | /** | |
98 | * sel_netport_insert - Insert a new port into the table | |
99 | * @port: the new port record | |
100 | * | |
101 | * Description: | |
c9b7b979 | 102 | * Add a new port record to the network address hash table. |
27cc2a6e JM |
103 | * |
104 | */ | |
c9b7b979 | 105 | static void sel_netport_insert(struct sel_netport *port) |
27cc2a6e JM |
106 | { |
107 | unsigned int idx; | |
108 | ||
109 | /* we need to impose a limit on the growth of the hash table so check | |
110 | * this bucket to make sure it is within the specified bounds */ | |
111 | idx = sel_netport_hashfn(port->psec.port); | |
112 | list_add_rcu(&port->list, &sel_netport_hash[idx].list); | |
113 | if (sel_netport_hash[idx].size == SEL_NETPORT_HASH_BKT_LIMIT) { | |
114 | struct sel_netport *tail; | |
c9b7b979 | 115 | tail = list_entry( |
50345f1e DH |
116 | rcu_dereference_protected( |
117 | sel_netport_hash[idx].list.prev, | |
118 | lockdep_is_held(&sel_netport_lock)), | |
c9b7b979 PM |
119 | struct sel_netport, list); |
120 | list_del_rcu(&tail->list); | |
449a68cc | 121 | kfree_rcu(tail, rcu); |
27cc2a6e JM |
122 | } else |
123 | sel_netport_hash[idx].size++; | |
27cc2a6e JM |
124 | } |
125 | ||
126 | /** | |
127 | * sel_netport_sid_slow - Lookup the SID of a network address using the policy | |
128 | * @protocol: protocol | |
129 | * @pnum: port | |
130 | * @sid: port SID | |
131 | * | |
132 | * Description: | |
133 | * This function determines the SID of a network port by quering the security | |
134 | * policy. The result is added to the network port table to speedup future | |
135 | * queries. Returns zero on success, negative values on failure. | |
136 | * | |
137 | */ | |
138 | static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) | |
139 | { | |
9b80c363 | 140 | int ret; |
27cc2a6e | 141 | struct sel_netport *port; |
9b80c363 | 142 | struct sel_netport *new; |
27cc2a6e JM |
143 | |
144 | spin_lock_bh(&sel_netport_lock); | |
145 | port = sel_netport_find(protocol, pnum); | |
146 | if (port != NULL) { | |
147 | *sid = port->psec.sid; | |
c9b7b979 PM |
148 | spin_unlock_bh(&sel_netport_lock); |
149 | return 0; | |
27cc2a6e | 150 | } |
9b80c363 | 151 | |
aa8e712c | 152 | ret = security_port_sid(&selinux_state, protocol, pnum, sid); |
27cc2a6e JM |
153 | if (ret != 0) |
154 | goto out; | |
9b80c363 PM |
155 | new = kzalloc(sizeof(*new), GFP_ATOMIC); |
156 | if (new) { | |
157 | new->psec.port = pnum; | |
158 | new->psec.protocol = protocol; | |
159 | new->psec.sid = *sid; | |
160 | sel_netport_insert(new); | |
161 | } | |
27cc2a6e JM |
162 | |
163 | out: | |
164 | spin_unlock_bh(&sel_netport_lock); | |
9b80c363 | 165 | if (unlikely(ret)) |
d006469d | 166 | pr_warn("SELinux: failure in %s(), unable to determine network port label\n", |
167 | __func__); | |
27cc2a6e JM |
168 | return ret; |
169 | } | |
170 | ||
171 | /** | |
172 | * sel_netport_sid - Lookup the SID of a network port | |
173 | * @protocol: protocol | |
174 | * @pnum: port | |
175 | * @sid: port SID | |
176 | * | |
177 | * Description: | |
178 | * This function determines the SID of a network port using the fastest method | |
179 | * possible. First the port table is queried, but if an entry can't be found | |
180 | * then the policy is queried and the result is added to the table to speedup | |
181 | * future queries. Returns zero on success, negative values on failure. | |
182 | * | |
183 | */ | |
184 | int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid) | |
185 | { | |
186 | struct sel_netport *port; | |
187 | ||
188 | rcu_read_lock(); | |
189 | port = sel_netport_find(protocol, pnum); | |
190 | if (port != NULL) { | |
191 | *sid = port->psec.sid; | |
192 | rcu_read_unlock(); | |
193 | return 0; | |
194 | } | |
195 | rcu_read_unlock(); | |
196 | ||
197 | return sel_netport_sid_slow(protocol, pnum, sid); | |
198 | } | |
199 | ||
200 | /** | |
201 | * sel_netport_flush - Flush the entire network port table | |
202 | * | |
203 | * Description: | |
204 | * Remove all entries from the network address table. | |
205 | * | |
206 | */ | |
615e51fd | 207 | void sel_netport_flush(void) |
27cc2a6e JM |
208 | { |
209 | unsigned int idx; | |
c9b7b979 | 210 | struct sel_netport *port, *port_tmp; |
27cc2a6e JM |
211 | |
212 | spin_lock_bh(&sel_netport_lock); | |
213 | for (idx = 0; idx < SEL_NETPORT_HASH_SIZE; idx++) { | |
c9b7b979 PM |
214 | list_for_each_entry_safe(port, port_tmp, |
215 | &sel_netport_hash[idx].list, list) { | |
27cc2a6e | 216 | list_del_rcu(&port->list); |
449a68cc | 217 | kfree_rcu(port, rcu); |
27cc2a6e JM |
218 | } |
219 | sel_netport_hash[idx].size = 0; | |
220 | } | |
221 | spin_unlock_bh(&sel_netport_lock); | |
222 | } | |
223 | ||
27cc2a6e JM |
224 | static __init int sel_netport_init(void) |
225 | { | |
226 | int iter; | |
27cc2a6e JM |
227 | |
228 | if (!selinux_enabled) | |
229 | return 0; | |
230 | ||
231 | for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) { | |
232 | INIT_LIST_HEAD(&sel_netport_hash[iter].list); | |
233 | sel_netport_hash[iter].size = 0; | |
234 | } | |
235 | ||
942ba364 | 236 | return 0; |
27cc2a6e JM |
237 | } |
238 | ||
239 | __initcall(sel_netport_init); |