diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index d91ab28718d4..471f3deeff7d 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -1133,6 +1133,19 @@ ip_local_reserved_ports - list of comma separated ranges Default: Empty +ip_local_unbindable_ports - list of comma separated ranges + Specify the ports which are not directly bind()able. + + Usually you would use this to block the use of ports which + are invalid due to something outside of the control of the + kernel. For example a port stolen by the nic for serial + console, remote power management or debugging. + + There's a relatively high chance you will also want to list + these ports in 'ip_local_reserved_ports' to prevent autobinding. + + Default: Empty + ip_unprivileged_port_start - INTEGER This is a per-namespace sysctl. It defines the first unprivileged port in the network namespace. Privileged ports diff --git a/include/net/ip.h b/include/net/ip.h index 0106c6590ee7..6a0fec83a3de 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -344,6 +344,13 @@ static inline bool inet_is_local_reserved_port(struct net *net, unsigned short p return test_bit(port, net->ipv4.sysctl_local_reserved_ports); } +static inline bool inet_is_local_unbindable_port(struct net *net, unsigned short port) +{ + if (!net->ipv4.sysctl_local_unbindable_ports) + return false; + return test_bit(port, net->ipv4.sysctl_local_unbindable_ports); +} + static inline bool sysctl_dev_name_is_allowed(const char *name) { return strcmp(name, "default") != 0 && strcmp(name, "all") != 0; @@ -360,6 +367,11 @@ static inline bool inet_is_local_reserved_port(struct net *net, unsigned short p return false; } +static inline bool inet_is_local_unbindable_port(struct net *net, unsigned short port) +{ + return false; +} + static inline bool inet_port_requires_bind_service(struct net *net, unsigned short port) { return port < PROT_SOCK; diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 2f972fccfdcd..9ee19a5e8669 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -199,6 +199,7 @@ struct netns_ipv4 { #ifdef CONFIG_SYSCTL unsigned long *sysctl_local_reserved_ports; + unsigned long *sysctl_local_unbindable_ports; int sysctl_ip_prot_sock; #endif diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 77534b44b8c7..d29c6464862d 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -500,6 +500,10 @@ int __inet_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len, goto out; snum = ntohs(addr->sin_port); + err = -EPERM; + if (snum && inet_is_local_unbindable_port(net, snum)) + goto out; + err = -EACCES; if (!(flags & BIND_NO_CAP_NET_BIND_SERVICE) && snum && inet_port_requires_bind_service(net, snum) && diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index 6f1e64d49232..c534e3a481a3 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -753,6 +753,13 @@ static struct ctl_table ipv4_net_table[] = { .mode = 0644, .proc_handler = proc_do_large_bitmap, }, + { + .procname = "ip_local_unbindable_ports", + .data = &init_net.ipv4.sysctl_local_unbindable_ports, + .maxlen = 65536, + .mode = 0644, + .proc_handler = proc_do_large_bitmap, + }, { .procname = "ip_no_pmtu_disc", .data = &init_net.ipv4.sysctl_ip_no_pmtu_disc, @@ -1442,11 +1449,17 @@ static __net_init int ipv4_sysctl_init_net(struct net *net) net->ipv4.sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL); if (!net->ipv4.sysctl_local_reserved_ports) - goto err_ports; + goto err_reserved_ports; + + net->ipv4.sysctl_local_unbindable_ports = kzalloc(65536 / 8, GFP_KERNEL); + if (!net->ipv4.sysctl_local_unbindable_ports) + goto err_unbindable_ports; return 0; -err_ports: +err_unbindable_ports: + kfree(net->ipv4.sysctl_local_reserved_ports); +err_reserved_ports: unregister_net_sysctl_table(net->ipv4.ipv4_hdr); err_reg: if (!net_eq(net, &init_net)) @@ -1459,6 +1472,7 @@ static __net_exit void ipv4_sysctl_exit_net(struct net *net) { struct ctl_table *table; + kfree(net->ipv4.sysctl_local_unbindable_ports); kfree(net->ipv4.sysctl_local_reserved_ports); table = net->ipv4.ipv4_hdr->ctl_table_arg; unregister_net_sysctl_table(net->ipv4.ipv4_hdr); diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index dab4a047590b..43f22464d21d 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -296,6 +296,8 @@ static int __inet6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len, return -EINVAL; snum = ntohs(addr->sin6_port); + if (snum && inet_is_local_unbindable_port(net, snum)) + return -EPERM; if (!(flags & BIND_NO_CAP_NET_BIND_SERVICE) && snum && inet_port_requires_bind_service(net, snum) && !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 5f6e6a6e91b3..17da10d39ae0 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -411,6 +411,9 @@ static int sctp_do_bind(struct sock *sk, union sctp_addr *addr, int len) } } + if (snum && inet_is_local_unbindable_port(net, snum)) + return -EPERM; + if (snum && inet_port_requires_bind_service(net, snum) && !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) return -EACCES; @@ -1079,6 +1082,8 @@ static int sctp_connect_new_asoc(struct sctp_endpoint *ep, if (sctp_autobind(sk)) return -EAGAIN; } else { + if (inet_is_local_unbindable_port(net, ep->base.bind_addr.port)) + return -EPERM; if (inet_port_requires_bind_service(net, ep->base.bind_addr.port) && !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) return -EACCES;