[PATCH linux 1/3] net/ipv4: split group_range logic to kernel/group_range.c

From: ~akihirosuda
Date: Tue May 30 2023 - 15:00:13 EST


From: Akihiro Suda <akihiro.suda.cz@xxxxxxxxxxxxx>

The logic can be reused for other sysctls in future.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@xxxxxxxxxxxxx>
---
include/linux/group_range.h | 24 ++++++++++
include/net/netns/ipv4.h | 9 +---
include/net/ping.h | 6 ---
kernel/Makefile | 2 +-
kernel/group_range.c | 91 +++++++++++++++++++++++++++++++++++++
net/ipv4/ping.c | 39 ++--------------
net/ipv4/sysctl_net_ipv4.c | 56 ++---------------------
7 files changed, 125 insertions(+), 102 deletions(-)
create mode 100644 include/linux/group_range.h
create mode 100644 kernel/group_range.c

diff --git a/include/linux/group_range.h b/include/linux/group_range.h
new file mode 100644
index 000000000000..5bd837eced95
--- /dev/null
+++ b/include/linux/group_range.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_GROUP_RANGE_H
+#define _LINUX_GROUP_RANGE_H
+
+#include <linux/seqlock.h>
+#include <linux/uidgid.h>
+
+/*
+ * gid_t is either uint or ushort. We want to pass it to
+ * proc_dointvec_minmax(), so it must not be larger than MAX_INT
+ */
+#define GROUP_RANGE_MAX (((gid_t)~0U) >> 1)
+
+struct group_range {
+ seqlock_t lock;
+ kgid_t range[2];
+};
+
+typedef struct group_range* (*sysctl_group_range_func_t)(struct ctl_table *);
+int sysctl_group_range(sysctl_group_range_func_t fn, struct ctl_table *table,
+ int write, void *buffer, size_t *lenp, loff_t *ppos);
+
+bool check_current_group_range(struct group_range *gr);
+#endif /* _LINUX_GROUP_RANGE_H */
diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h
index db762e35aca9..75d745a7c6e1 100644
--- a/include/net/netns/ipv4.h
+++ b/include/net/netns/ipv4.h
@@ -6,11 +6,11 @@
#ifndef __NETNS_IPV4_H__
#define __NETNS_IPV4_H__

-#include <linux/uidgid.h>
#include <net/inet_frag.h>
#include <linux/rcupdate.h>
#include <linux/seqlock.h>
#include <linux/siphash.h>
+#include <linux/group_range.h>

struct ctl_table_header;
struct ipv4_devconf;
@@ -24,11 +24,6 @@ struct local_ports {
bool warned;
};

-struct ping_group_range {
- seqlock_t lock;
- kgid_t range[2];
-};
-
struct inet_hashinfo;

struct inet_timewait_death_row {
@@ -204,7 +199,7 @@ struct netns_ipv4 {
int sysctl_igmp_max_msf;
int sysctl_igmp_qrv;

- struct ping_group_range ping_group_range;
+ struct group_range ping_group_range;

atomic_t dev_addr_genid;

diff --git a/include/net/ping.h b/include/net/ping.h
index 9233ad3de0ad..37b1d7baeb7b 100644
--- a/include/net/ping.h
+++ b/include/net/ping.h
@@ -16,12 +16,6 @@
#define PING_HTABLE_SIZE 64
#define PING_HTABLE_MASK (PING_HTABLE_SIZE-1)

-/*
- * gid_t is either uint or ushort. We want to pass it to
- * proc_dointvec_minmax(), so it must not be larger than MAX_INT
- */
-#define GID_T_MAX (((gid_t)~0U) >> 1)
-
/* Compatibility glue so we can support IPv6 when it's compiled as a module */
struct pingv6_ops {
int (*ipv6_recv_error)(struct sock *sk, struct msghdr *msg, int len,
diff --git a/kernel/Makefile b/kernel/Makefile
index b69c95315480..fb3a812cf92e 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -10,7 +10,7 @@ obj-y = fork.o exec_domain.o panic.o \
extable.o params.o \
kthread.o sys_ni.o nsproxy.o \
notifier.o ksysfs.o cred.o reboot.o \
- async.o range.o smpboot.o ucount.o regset.o
+ async.o range.o smpboot.o ucount.o regset.o group_range.o

obj-$(CONFIG_USERMODE_DRIVER) += usermode_driver.o
obj-$(CONFIG_MULTIUSER) += groups.o
diff --git a/kernel/group_range.c b/kernel/group_range.c
new file mode 100644
index 000000000000..b5c7d35d680b
--- /dev/null
+++ b/kernel/group_range.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/cred.h>
+#include <linux/group_range.h>
+#include <linux/uidgid.h>
+#include <linux/seqlock.h>
+#include <linux/sysctl.h>
+
+static void get_group_range(struct group_range *gr, kgid_t *low, kgid_t *high)
+{
+ unsigned int seq;
+
+ do {
+ seq = read_seqbegin(&gr->lock);
+
+ *low = gr->range[0];
+ *high = gr->range[1];
+ } while (read_seqretry(&gr->lock, seq));
+}
+
+static void set_group_range(struct group_range *gr, kgid_t low, kgid_t high)
+{
+ write_seqlock(&gr->lock);
+ gr->range[0] = low;
+ gr->range[1] = high;
+ write_sequnlock(&gr->lock);
+}
+
+static int group_range_min[] = { 0, 0 };
+static int group_range_max[] = { GROUP_RANGE_MAX, GROUP_RANGE_MAX };
+
+int sysctl_group_range(sysctl_group_range_func_t fn, struct ctl_table *table,
+ int write, void *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct group_range *gr = fn(table);
+ struct user_namespace *user_ns = current_user_ns();
+ int ret;
+ gid_t urange[2];
+ kgid_t low, high;
+ struct ctl_table tmp = {
+ .data = &urange,
+ .maxlen = sizeof(urange),
+ .mode = table->mode,
+ .extra1 = &group_range_min,
+ .extra2 = &group_range_max,
+ };
+
+ get_group_range(gr, &low, &high);
+ urange[0] = from_kgid_munged(user_ns, low);
+ urange[1] = from_kgid_munged(user_ns, high);
+ ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
+
+ if (write && ret == 0) {
+ low = make_kgid(user_ns, urange[0]);
+ high = make_kgid(user_ns, urange[1]);
+ if (!gid_valid(low) || !gid_valid(high))
+ return -EINVAL;
+ if (urange[1] < urange[0] || gid_lt(high, low)) {
+ low = make_kgid(&init_user_ns, 1);
+ high = make_kgid(&init_user_ns, 0);
+ }
+ set_group_range(gr, low, high);
+ }
+
+ return ret;
+}
+
+bool check_current_group_range(struct group_range *gr)
+{
+ kgid_t group = current_egid();
+ struct group_info *group_info;
+ int i;
+ kgid_t low, high;
+ bool ret = true;
+
+ get_group_range(gr, &low, &high);
+ if (gid_lte(low, group) && gid_lte(group, high))
+ return true;
+
+ group_info = get_current_groups();
+ for (i = 0; i < group_info->ngroups; i++) {
+ kgid_t gid = group_info->gid[i];
+
+ if (gid_lte(low, gid) && gid_lte(gid, high))
+ goto out_release_group;
+ }
+ ret = false;
+out_release_group:
+ put_group_info(group_info);
+ return ret;
+}
diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c
index 5178a3f3cb53..6e23771c5234 100644
--- a/net/ipv4/ping.c
+++ b/net/ipv4/ping.c
@@ -244,50 +244,17 @@ exit:
return sk;
}

-static void inet_get_ping_group_range_net(struct net *net, kgid_t *low,
- kgid_t *high)
-{
- kgid_t *data = net->ipv4.ping_group_range.range;
- unsigned int seq;
-
- do {
- seq = read_seqbegin(&net->ipv4.ping_group_range.lock);
-
- *low = data[0];
- *high = data[1];
- } while (read_seqretry(&net->ipv4.ping_group_range.lock, seq));
-}
-
-
int ping_init_sock(struct sock *sk)
{
struct net *net = sock_net(sk);
- kgid_t group = current_egid();
- struct group_info *group_info;
- int i;
- kgid_t low, high;
- int ret = 0;

if (sk->sk_family == AF_INET6)
sk->sk_ipv6only = 1;

- inet_get_ping_group_range_net(net, &low, &high);
- if (gid_lte(low, group) && gid_lte(group, high))
- return 0;
-
- group_info = get_current_groups();
- for (i = 0; i < group_info->ngroups; i++) {
- kgid_t gid = group_info->gid[i];
+ if (!check_current_group_range(&net->ipv4.ping_group_range))
+ return -EACCES;

- if (gid_lte(low, gid) && gid_lte(gid, high))
- goto out_release_group;
- }
-
- ret = -EACCES;
-
-out_release_group:
- put_group_info(group_info);
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(ping_init_sock);

diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c
index 40fe70fc2015..ad355ab265db 100644
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -34,8 +34,6 @@ static int ip_ttl_min = 1;
static int ip_ttl_max = 255;
static int tcp_syn_retries_min = 1;
static int tcp_syn_retries_max = MAX_TCP_SYNCNT;
-static int ip_ping_group_range_min[] = { 0, 0 };
-static int ip_ping_group_range_max[] = { GID_T_MAX, GID_T_MAX };
static u32 u32_max_div_HZ = UINT_MAX / HZ;
static int one_day_secs = 24 * 3600;
static u32 fib_multipath_hash_fields_all_mask __maybe_unused =
@@ -133,66 +131,20 @@ static int ipv4_privileged_ports(struct ctl_table *table, int write,
return ret;
}

-static void inet_get_ping_group_range_table(struct ctl_table *table, kgid_t *low, kgid_t *high)
+static struct group_range *ipv4_ping_group_range_func(struct ctl_table *table)
{
- kgid_t *data = table->data;
struct net *net =
container_of(table->data, struct net, ipv4.ping_group_range.range);
- unsigned int seq;
- do {
- seq = read_seqbegin(&net->ipv4.ping_group_range.lock);

- *low = data[0];
- *high = data[1];
- } while (read_seqretry(&net->ipv4.ping_group_range.lock, seq));
-}
-
-/* Update system visible IP port range */
-static void set_ping_group_range(struct ctl_table *table, kgid_t low, kgid_t high)
-{
- kgid_t *data = table->data;
- struct net *net =
- container_of(table->data, struct net, ipv4.ping_group_range.range);
- write_seqlock(&net->ipv4.ping_group_range.lock);
- data[0] = low;
- data[1] = high;
- write_sequnlock(&net->ipv4.ping_group_range.lock);
+ return &net->ipv4.ping_group_range;
}

/* Validate changes from /proc interface. */
static int ipv4_ping_group_range(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
- struct user_namespace *user_ns = current_user_ns();
- int ret;
- gid_t urange[2];
- kgid_t low, high;
- struct ctl_table tmp = {
- .data = &urange,
- .maxlen = sizeof(urange),
- .mode = table->mode,
- .extra1 = &ip_ping_group_range_min,
- .extra2 = &ip_ping_group_range_max,
- };
-
- inet_get_ping_group_range_table(table, &low, &high);
- urange[0] = from_kgid_munged(user_ns, low);
- urange[1] = from_kgid_munged(user_ns, high);
- ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
-
- if (write && ret == 0) {
- low = make_kgid(user_ns, urange[0]);
- high = make_kgid(user_ns, urange[1]);
- if (!gid_valid(low) || !gid_valid(high))
- return -EINVAL;
- if (urange[1] < urange[0] || gid_lt(high, low)) {
- low = make_kgid(&init_user_ns, 1);
- high = make_kgid(&init_user_ns, 0);
- }
- set_ping_group_range(table, low, high);
- }
-
- return ret;
+ return sysctl_group_range(ipv4_ping_group_range_func, table,
+ write, buffer, lenp, ppos);
}

static int ipv4_fwd_update_priority(struct ctl_table *table, int write,
--
2.38.4