aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Hemminger <shemminger@linux-foundation.org>2007-10-10 20:30:46 -0400
committerDavid S. Miller <davem@davemloft.net>2007-10-10 20:30:46 -0400
commit227b60f5102cda4e4ab792b526a59c8cb20cd9f8 (patch)
tree2c9e372601ba794894833b0618bc531a9f5d57c4
parent06393009000779b00a558fd2f280882cc7dc2008 (diff)
[INET]: local port range robustness
Expansion of original idea from Denis V. Lunev <den@openvz.org> Add robustness and locking to the local_port_range sysctl. 1. Enforce that low < high when setting. 2. Use seqlock to ensure atomic update. The locking might seem like overkill, but there are cases where sysadmin might want to change value in the middle of a DoS attack. Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/infiniband/core/cma.c22
-rw-r--r--include/net/ip.h3
-rw-r--r--net/ipv4/inet_connection_sock.c22
-rw-r--r--net/ipv4/inet_hashtables.c13
-rw-r--r--net/ipv4/sysctl_net_ipv4.c75
-rw-r--r--net/ipv4/tcp_ipv4.c1
-rw-r--r--net/ipv4/udp.c6
-rw-r--r--net/ipv6/inet6_hashtables.c12
-rw-r--r--net/sctp/socket.c11
-rw-r--r--security/selinux/hooks.c39
10 files changed, 146 insertions, 58 deletions
diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c
index 9ffb9987450a..2e641b255db4 100644
--- a/drivers/infiniband/core/cma.c
+++ b/drivers/infiniband/core/cma.c
@@ -1866,13 +1866,14 @@ err1:
1866static int cma_alloc_any_port(struct idr *ps, struct rdma_id_private *id_priv) 1866static int cma_alloc_any_port(struct idr *ps, struct rdma_id_private *id_priv)
1867{ 1867{
1868 struct rdma_bind_list *bind_list; 1868 struct rdma_bind_list *bind_list;
1869 int port, ret; 1869 int port, ret, low, high;
1870 1870
1871 bind_list = kzalloc(sizeof *bind_list, GFP_KERNEL); 1871 bind_list = kzalloc(sizeof *bind_list, GFP_KERNEL);
1872 if (!bind_list) 1872 if (!bind_list)
1873 return -ENOMEM; 1873 return -ENOMEM;
1874 1874
1875retry: 1875retry:
1876 /* FIXME: add proper port randomization per like inet_csk_get_port */
1876 do { 1877 do {
1877 ret = idr_get_new_above(ps, bind_list, next_port, &port); 1878 ret = idr_get_new_above(ps, bind_list, next_port, &port);
1878 } while ((ret == -EAGAIN) && idr_pre_get(ps, GFP_KERNEL)); 1879 } while ((ret == -EAGAIN) && idr_pre_get(ps, GFP_KERNEL));
@@ -1880,18 +1881,19 @@ retry:
1880 if (ret) 1881 if (ret)
1881 goto err1; 1882 goto err1;
1882 1883
1883 if (port > sysctl_local_port_range[1]) { 1884 inet_get_local_port_range(&low, &high);
1884 if (next_port != sysctl_local_port_range[0]) { 1885 if (port > high) {
1886 if (next_port != low) {
1885 idr_remove(ps, port); 1887 idr_remove(ps, port);
1886 next_port = sysctl_local_port_range[0]; 1888 next_port = low;
1887 goto retry; 1889 goto retry;
1888 } 1890 }
1889 ret = -EADDRNOTAVAIL; 1891 ret = -EADDRNOTAVAIL;
1890 goto err2; 1892 goto err2;
1891 } 1893 }
1892 1894
1893 if (port == sysctl_local_port_range[1]) 1895 if (port == high)
1894 next_port = sysctl_local_port_range[0]; 1896 next_port = low;
1895 else 1897 else
1896 next_port = port + 1; 1898 next_port = port + 1;
1897 1899
@@ -2769,12 +2771,12 @@ static void cma_remove_one(struct ib_device *device)
2769 2771
2770static int cma_init(void) 2772static int cma_init(void)
2771{ 2773{
2772 int ret; 2774 int ret, low, high;
2773 2775
2774 get_random_bytes(&next_port, sizeof next_port); 2776 get_random_bytes(&next_port, sizeof next_port);
2775 next_port = ((unsigned int) next_port % 2777 inet_get_local_port_range(&low, &high);
2776 (sysctl_local_port_range[1] - sysctl_local_port_range[0])) + 2778 next_port = ((unsigned int) next_port % (high - low)) + low;
2777 sysctl_local_port_range[0]; 2779
2778 cma_wq = create_singlethread_workqueue("rdma_cm"); 2780 cma_wq = create_singlethread_workqueue("rdma_cm");
2779 if (!cma_wq) 2781 if (!cma_wq)
2780 return -ENOMEM; 2782 return -ENOMEM;
diff --git a/include/net/ip.h b/include/net/ip.h
index abf2820a1125..3af3ed9d320b 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -171,7 +171,8 @@ extern unsigned long snmp_fold_field(void *mib[], int offt);
171extern int snmp_mib_init(void *ptr[2], size_t mibsize, size_t mibalign); 171extern int snmp_mib_init(void *ptr[2], size_t mibsize, size_t mibalign);
172extern void snmp_mib_free(void *ptr[2]); 172extern void snmp_mib_free(void *ptr[2]);
173 173
174extern int sysctl_local_port_range[2]; 174extern void inet_get_local_port_range(int *low, int *high);
175
175extern int sysctl_ip_default_ttl; 176extern int sysctl_ip_default_ttl;
176extern int sysctl_ip_nonlocal_bind; 177extern int sysctl_ip_nonlocal_bind;
177 178
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index fbe7714f21d0..3cef12835c4b 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -33,6 +33,19 @@ EXPORT_SYMBOL(inet_csk_timer_bug_msg);
33 * This array holds the first and last local port number. 33 * This array holds the first and last local port number.
34 */ 34 */
35int sysctl_local_port_range[2] = { 32768, 61000 }; 35int sysctl_local_port_range[2] = { 32768, 61000 };
36DEFINE_SEQLOCK(sysctl_port_range_lock);
37
38void inet_get_local_port_range(int *low, int *high)
39{
40 unsigned seq;
41 do {
42 seq = read_seqbegin(&sysctl_port_range_lock);
43
44 *low = sysctl_local_port_range[0];
45 *high = sysctl_local_port_range[1];
46 } while (read_seqretry(&sysctl_port_range_lock, seq));
47}
48EXPORT_SYMBOL(inet_get_local_port_range);
36 49
37int inet_csk_bind_conflict(const struct sock *sk, 50int inet_csk_bind_conflict(const struct sock *sk,
38 const struct inet_bind_bucket *tb) 51 const struct inet_bind_bucket *tb)
@@ -77,10 +90,11 @@ int inet_csk_get_port(struct inet_hashinfo *hashinfo,
77 90
78 local_bh_disable(); 91 local_bh_disable();
79 if (!snum) { 92 if (!snum) {
80 int low = sysctl_local_port_range[0]; 93 int remaining, rover, low, high;
81 int high = sysctl_local_port_range[1]; 94
82 int remaining = (high - low) + 1; 95 inet_get_local_port_range(&low, &high);
83 int rover = net_random() % (high - low) + low; 96 remaining = high - low;
97 rover = net_random() % remaining + low;
84 98
85 do { 99 do {
86 head = &hashinfo->bhash[inet_bhashfn(rover, hashinfo->bhash_size)]; 100 head = &hashinfo->bhash[inet_bhashfn(rover, hashinfo->bhash_size)];
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index fb662621c54e..fac6398e4367 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -279,19 +279,18 @@ int inet_hash_connect(struct inet_timewait_death_row *death_row,
279 int ret; 279 int ret;
280 280
281 if (!snum) { 281 if (!snum) {
282 int low = sysctl_local_port_range[0]; 282 int i, remaining, low, high, port;
283 int high = sysctl_local_port_range[1];
284 int range = high - low;
285 int i;
286 int port;
287 static u32 hint; 283 static u32 hint;
288 u32 offset = hint + inet_sk_port_offset(sk); 284 u32 offset = hint + inet_sk_port_offset(sk);
289 struct hlist_node *node; 285 struct hlist_node *node;
290 struct inet_timewait_sock *tw = NULL; 286 struct inet_timewait_sock *tw = NULL;
291 287
288 inet_get_local_port_range(&low, &high);
289 remaining = high - low;
290
292 local_bh_disable(); 291 local_bh_disable();
293 for (i = 1; i <= range; i++) { 292 for (i = 1; i <= remaining; i++) {
294 port = low + (i + offset) % range; 293 port = low + (i + offset) % remaining;
295 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)]; 294 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)];
296 spin_lock(&head->lock); 295 spin_lock(&head->lock);
297 296
diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 53ef0f4bbdaa..eb286abcf5dc 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -12,6 +12,7 @@
12#include <linux/sysctl.h> 12#include <linux/sysctl.h>
13#include <linux/igmp.h> 13#include <linux/igmp.h>
14#include <linux/inetdevice.h> 14#include <linux/inetdevice.h>
15#include <linux/seqlock.h>
15#include <net/snmp.h> 16#include <net/snmp.h>
16#include <net/icmp.h> 17#include <net/icmp.h>
17#include <net/ip.h> 18#include <net/ip.h>
@@ -89,6 +90,74 @@ static int ipv4_sysctl_forward_strategy(ctl_table *table,
89 return 1; 90 return 1;
90} 91}
91 92
93extern seqlock_t sysctl_port_range_lock;
94extern int sysctl_local_port_range[2];
95
96/* Update system visible IP port range */
97static void set_local_port_range(int range[2])
98{
99 write_seqlock(&sysctl_port_range_lock);
100 sysctl_local_port_range[0] = range[0];
101 sysctl_local_port_range[1] = range[1];
102 write_sequnlock(&sysctl_port_range_lock);
103}
104
105/* Validate changes from /proc interface. */
106static int ipv4_local_port_range(ctl_table *table, int write, struct file *filp,
107 void __user *buffer,
108 size_t *lenp, loff_t *ppos)
109{
110 int ret;
111 int range[2] = { sysctl_local_port_range[0],
112 sysctl_local_port_range[1] };
113 ctl_table tmp = {
114 .data = &range,
115 .maxlen = sizeof(range),
116 .mode = table->mode,
117 .extra1 = &ip_local_port_range_min,
118 .extra2 = &ip_local_port_range_max,
119 };
120
121 ret = proc_dointvec_minmax(&tmp, write, filp, buffer, lenp, ppos);
122
123 if (write && ret == 0) {
124 if (range[1] <= range[0])
125 ret = -EINVAL;
126 else
127 set_local_port_range(range);
128 }
129
130 return ret;
131}
132
133/* Validate changes from sysctl interface. */
134static int ipv4_sysctl_local_port_range(ctl_table *table, int __user *name,
135 int nlen, void __user *oldval,
136 size_t __user *oldlenp,
137 void __user *newval, size_t newlen)
138{
139 int ret;
140 int range[2] = { sysctl_local_port_range[0],
141 sysctl_local_port_range[1] };
142 ctl_table tmp = {
143 .data = &range,
144 .maxlen = sizeof(range),
145 .mode = table->mode,
146 .extra1 = &ip_local_port_range_min,
147 .extra2 = &ip_local_port_range_max,
148 };
149
150 ret = sysctl_intvec(&tmp, name, nlen, oldval, oldlenp, newval, newlen);
151 if (ret == 0 && newval && newlen) {
152 if (range[1] <= range[0])
153 ret = -EINVAL;
154 else
155 set_local_port_range(range);
156 }
157 return ret;
158}
159
160
92static int proc_tcp_congestion_control(ctl_table *ctl, int write, struct file * filp, 161static int proc_tcp_congestion_control(ctl_table *ctl, int write, struct file * filp,
93 void __user *buffer, size_t *lenp, loff_t *ppos) 162 void __user *buffer, size_t *lenp, loff_t *ppos)
94{ 163{
@@ -427,10 +496,8 @@ ctl_table ipv4_table[] = {
427 .data = &sysctl_local_port_range, 496 .data = &sysctl_local_port_range,
428 .maxlen = sizeof(sysctl_local_port_range), 497 .maxlen = sizeof(sysctl_local_port_range),
429 .mode = 0644, 498 .mode = 0644,
430 .proc_handler = &proc_dointvec_minmax, 499 .proc_handler = &ipv4_local_port_range,
431 .strategy = &sysctl_intvec, 500 .strategy = &ipv4_sysctl_local_port_range,
432 .extra1 = ip_local_port_range_min,
433 .extra2 = ip_local_port_range_max
434 }, 501 },
435 { 502 {
436 .ctl_name = NET_IPV4_ICMP_ECHO_IGNORE_ALL, 503 .ctl_name = NET_IPV4_ICMP_ECHO_IGNORE_ALL,
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 8855e640e958..38cf73a56731 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -2470,6 +2470,5 @@ EXPORT_SYMBOL(tcp_v4_syn_recv_sock);
2470EXPORT_SYMBOL(tcp_proc_register); 2470EXPORT_SYMBOL(tcp_proc_register);
2471EXPORT_SYMBOL(tcp_proc_unregister); 2471EXPORT_SYMBOL(tcp_proc_unregister);
2472#endif 2472#endif
2473EXPORT_SYMBOL(sysctl_local_port_range);
2474EXPORT_SYMBOL(sysctl_tcp_low_latency); 2473EXPORT_SYMBOL(sysctl_tcp_low_latency);
2475 2474
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index ef4d901ee9ad..cb9fc58efb2f 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -147,11 +147,11 @@ int __udp_lib_get_port(struct sock *sk, unsigned short snum,
147 write_lock_bh(&udp_hash_lock); 147 write_lock_bh(&udp_hash_lock);
148 148
149 if (!snum) { 149 if (!snum) {
150 int i; 150 int i, low, high;
151 int low = sysctl_local_port_range[0];
152 int high = sysctl_local_port_range[1];
153 unsigned rover, best, best_size_so_far; 151 unsigned rover, best, best_size_so_far;
154 152
153 inet_get_local_port_range(&low, &high);
154
155 best_size_so_far = UINT_MAX; 155 best_size_so_far = UINT_MAX;
156 best = rover = net_random() % (high - low) + low; 156 best = rover = net_random() % (high - low) + low;
157 157
diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c
index ae6b0e7eb488..1c2c27655435 100644
--- a/net/ipv6/inet6_hashtables.c
+++ b/net/ipv6/inet6_hashtables.c
@@ -254,18 +254,18 @@ int inet6_hash_connect(struct inet_timewait_death_row *death_row,
254 int ret; 254 int ret;
255 255
256 if (snum == 0) { 256 if (snum == 0) {
257 const int low = sysctl_local_port_range[0]; 257 int i, port, low, high, remaining;
258 const int high = sysctl_local_port_range[1];
259 const int range = high - low;
260 int i, port;
261 static u32 hint; 258 static u32 hint;
262 const u32 offset = hint + inet6_sk_port_offset(sk); 259 const u32 offset = hint + inet6_sk_port_offset(sk);
263 struct hlist_node *node; 260 struct hlist_node *node;
264 struct inet_timewait_sock *tw = NULL; 261 struct inet_timewait_sock *tw = NULL;
265 262
263 inet_get_local_port_range(&low, &high);
264 remaining = high - low;
265
266 local_bh_disable(); 266 local_bh_disable();
267 for (i = 1; i <= range; i++) { 267 for (i = 1; i <= remaining; i++) {
268 port = low + (i + offset) % range; 268 port = low + (i + offset) % remaining;
269 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)]; 269 head = &hinfo->bhash[inet_bhashfn(port, hinfo->bhash_size)];
270 spin_lock(&head->lock); 270 spin_lock(&head->lock);
271 271
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index 7cd58ef84eda..9c6a4b5f6264 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -5315,11 +5315,12 @@ static long sctp_get_port_local(struct sock *sk, union sctp_addr *addr)
5315 5315
5316 if (snum == 0) { 5316 if (snum == 0) {
5317 /* Search for an available port. */ 5317 /* Search for an available port. */
5318 unsigned int low = sysctl_local_port_range[0]; 5318 int low, high, remaining, index;
5319 unsigned int high = sysctl_local_port_range[1]; 5319 unsigned int rover;
5320 unsigned int remaining = (high - low) + 1; 5320
5321 unsigned int rover = net_random() % remaining + low; 5321 inet_get_local_port_range(&low, &high);
5322 int index; 5322 remaining = (high - low) + 1;
5323 rover = net_random() % remaining + low;
5323 5324
5324 do { 5325 do {
5325 rover++; 5326 rover++;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 0753b20e23fe..3c3fff33d1ce 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -47,7 +47,7 @@
47#include <linux/netfilter_ipv6.h> 47#include <linux/netfilter_ipv6.h>
48#include <linux/tty.h> 48#include <linux/tty.h>
49#include <net/icmp.h> 49#include <net/icmp.h>
50#include <net/ip.h> /* for sysctl_local_port_range[] */ 50#include <net/ip.h> /* for local_port_range[] */
51#include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */ 51#include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */
52#include <asm/uaccess.h> 52#include <asm/uaccess.h>
53#include <asm/ioctls.h> 53#include <asm/ioctls.h>
@@ -3232,8 +3232,6 @@ static int selinux_socket_post_create(struct socket *sock, int family,
3232/* Range of port numbers used to automatically bind. 3232/* Range of port numbers used to automatically bind.
3233 Need to determine whether we should perform a name_bind 3233 Need to determine whether we should perform a name_bind
3234 permission check between the socket and the port number. */ 3234 permission check between the socket and the port number. */
3235#define ip_local_port_range_0 sysctl_local_port_range[0]
3236#define ip_local_port_range_1 sysctl_local_port_range[1]
3237 3235
3238static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) 3236static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen)
3239{ 3237{
@@ -3276,20 +3274,27 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
3276 addrp = (char *)&addr6->sin6_addr.s6_addr; 3274 addrp = (char *)&addr6->sin6_addr.s6_addr;
3277 } 3275 }
3278 3276
3279 if (snum&&(snum < max(PROT_SOCK,ip_local_port_range_0) || 3277 if (snum) {
3280 snum > ip_local_port_range_1)) { 3278 int low, high;
3281 err = security_port_sid(sk->sk_family, sk->sk_type, 3279
3282 sk->sk_protocol, snum, &sid); 3280 inet_get_local_port_range(&low, &high);
3283 if (err) 3281
3284 goto out; 3282 if (snum < max(PROT_SOCK, low) || snum > high) {
3285 AVC_AUDIT_DATA_INIT(&ad,NET); 3283 err = security_port_sid(sk->sk_family,
3286 ad.u.net.sport = htons(snum); 3284 sk->sk_type,
3287 ad.u.net.family = family; 3285 sk->sk_protocol, snum,
3288 err = avc_has_perm(isec->sid, sid, 3286 &sid);
3289 isec->sclass, 3287 if (err)
3290 SOCKET__NAME_BIND, &ad); 3288 goto out;
3291 if (err) 3289 AVC_AUDIT_DATA_INIT(&ad,NET);
3292 goto out; 3290 ad.u.net.sport = htons(snum);
3291 ad.u.net.family = family;
3292 err = avc_has_perm(isec->sid, sid,
3293 isec->sclass,
3294 SOCKET__NAME_BIND, &ad);
3295 if (err)
3296 goto out;
3297 }
3293 } 3298 }
3294 3299
3295 switch(isec->sclass) { 3300 switch(isec->sclass) {