1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Xtables module to match the process control group.
4  *
5  * Might be used to implement individual "per-application" firewall
6  * policies in contrast to global policies based on control groups.
7  * Matching is based upon processes tagged to net_cls' classid marker.
8  *
9  * (C) 2013 Daniel Borkmann <dborkman@redhat.com>
10  */
11 
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13 
14 #include <linux/skbuff.h>
15 #include <linux/module.h>
16 #include <linux/netfilter/x_tables.h>
17 #include <linux/netfilter/xt_cgroup.h>
18 #include <net/sock.h>
19 
20 MODULE_LICENSE("GPL");
21 MODULE_AUTHOR("Daniel Borkmann <dborkman@redhat.com>");
22 MODULE_DESCRIPTION("Xtables: process control group matching");
23 MODULE_ALIAS("ipt_cgroup");
24 MODULE_ALIAS("ip6t_cgroup");
25 
26 #define NET_CLS_CLASSID_INVALID_MSG "xt_cgroup: classid invalid without net_cls cgroups\n"
27 
28 static int cgroup_mt_check_v0(const struct xt_mtchk_param *par)
29 {
30 	struct xt_cgroup_info_v0 *info = par->matchinfo;
31 
32 	if (info->invert & ~1)
33 		return -EINVAL;
34 
35 	if (!IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) {
36 		pr_info(NET_CLS_CLASSID_INVALID_MSG);
37 		return -EINVAL;
38 	}
39 
40 	return 0;
41 }
42 
43 static int cgroup_mt_check_v1(const struct xt_mtchk_param *par)
44 {
45 	struct xt_cgroup_info_v1 *info = par->matchinfo;
46 	struct cgroup *cgrp;
47 
48 	if ((info->invert_path & ~1) || (info->invert_classid & ~1))
49 		return -EINVAL;
50 
51 	if (!info->has_path && !info->has_classid) {
52 		pr_info("xt_cgroup: no path or classid specified\n");
53 		return -EINVAL;
54 	}
55 
56 	if (info->has_path && info->has_classid) {
57 		pr_info_ratelimited("path and classid specified\n");
58 		return -EINVAL;
59 	}
60 
61 	if (info->has_classid && !IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) {
62 		pr_info(NET_CLS_CLASSID_INVALID_MSG);
63 		return -EINVAL;
64 	}
65 
66 	info->priv = NULL;
67 	if (info->has_path) {
68 		cgrp = cgroup_get_from_path(info->path);
69 		if (IS_ERR(cgrp)) {
70 			pr_info_ratelimited("invalid path, errno=%ld\n",
71 					    PTR_ERR(cgrp));
72 			return -EINVAL;
73 		}
74 		info->priv = cgrp;
75 	}
76 
77 	return 0;
78 }
79 
80 static int cgroup_mt_check_v2(const struct xt_mtchk_param *par)
81 {
82 	struct xt_cgroup_info_v2 *info = par->matchinfo;
83 	struct cgroup *cgrp;
84 
85 	if ((info->invert_path & ~1) || (info->invert_classid & ~1))
86 		return -EINVAL;
87 
88 	if (!info->has_path && !info->has_classid) {
89 		pr_info("xt_cgroup: no path or classid specified\n");
90 		return -EINVAL;
91 	}
92 
93 	if (info->has_path && info->has_classid) {
94 		pr_info_ratelimited("path and classid specified\n");
95 		return -EINVAL;
96 	}
97 
98 	if (info->has_classid && !IS_ENABLED(CONFIG_CGROUP_NET_CLASSID)) {
99 		pr_info(NET_CLS_CLASSID_INVALID_MSG);
100 		return -EINVAL;
101 	}
102 
103 	info->priv = NULL;
104 	if (info->has_path) {
105 		cgrp = cgroup_get_from_path(info->path);
106 		if (IS_ERR(cgrp)) {
107 			pr_info_ratelimited("invalid path, errno=%ld\n",
108 					    PTR_ERR(cgrp));
109 			return -EINVAL;
110 		}
111 		info->priv = cgrp;
112 	}
113 
114 	return 0;
115 }
116 
117 static bool
118 cgroup_mt_v0(const struct sk_buff *skb, struct xt_action_param *par)
119 {
120 #ifdef CONFIG_CGROUP_NET_CLASSID
121 	const struct xt_cgroup_info_v0 *info = par->matchinfo;
122 	struct sock *sk = skb->sk;
123 
124 	if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk)))
125 		return false;
126 
127 	return (info->id == sock_cgroup_classid(&skb->sk->sk_cgrp_data)) ^
128 		info->invert;
129 #endif
130 	return false;
131 }
132 
133 static bool cgroup_mt_v1(const struct sk_buff *skb, struct xt_action_param *par)
134 {
135 	const struct xt_cgroup_info_v1 *info = par->matchinfo;
136 	struct sock_cgroup_data *skcd = &skb->sk->sk_cgrp_data;
137 	struct cgroup *ancestor = info->priv;
138 	struct sock *sk = skb->sk;
139 
140 	if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk)))
141 		return false;
142 
143 	if (ancestor)
144 		return cgroup_is_descendant(sock_cgroup_ptr(skcd), ancestor) ^
145 			info->invert_path;
146 #ifdef CONFIG_CGROUP_NET_CLASSID
147 	else
148 		return (info->classid == sock_cgroup_classid(skcd)) ^
149 			info->invert_classid;
150 #endif
151 	return false;
152 }
153 
154 static bool cgroup_mt_v2(const struct sk_buff *skb, struct xt_action_param *par)
155 {
156 	const struct xt_cgroup_info_v2 *info = par->matchinfo;
157 	struct sock_cgroup_data *skcd = &skb->sk->sk_cgrp_data;
158 	struct cgroup *ancestor = info->priv;
159 	struct sock *sk = skb->sk;
160 
161 	if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk)))
162 		return false;
163 
164 	if (ancestor)
165 		return cgroup_is_descendant(sock_cgroup_ptr(skcd), ancestor) ^
166 			info->invert_path;
167 #ifdef CONFIG_CGROUP_NET_CLASSID
168 	else
169 		return (info->classid == sock_cgroup_classid(skcd)) ^
170 			info->invert_classid;
171 #endif
172 	return false;
173 }
174 
175 static void cgroup_mt_destroy_v1(const struct xt_mtdtor_param *par)
176 {
177 	struct xt_cgroup_info_v1 *info = par->matchinfo;
178 
179 	if (info->priv)
180 		cgroup_put(info->priv);
181 }
182 
183 static void cgroup_mt_destroy_v2(const struct xt_mtdtor_param *par)
184 {
185 	struct xt_cgroup_info_v2 *info = par->matchinfo;
186 
187 	if (info->priv)
188 		cgroup_put(info->priv);
189 }
190 
191 static struct xt_match cgroup_mt_reg[] __read_mostly = {
192 	{
193 		.name		= "cgroup",
194 		.revision	= 0,
195 		.family		= NFPROTO_UNSPEC,
196 		.checkentry	= cgroup_mt_check_v0,
197 		.match		= cgroup_mt_v0,
198 		.matchsize	= sizeof(struct xt_cgroup_info_v0),
199 		.me		= THIS_MODULE,
200 		.hooks		= (1 << NF_INET_LOCAL_OUT) |
201 				  (1 << NF_INET_POST_ROUTING) |
202 				  (1 << NF_INET_LOCAL_IN),
203 	},
204 	{
205 		.name		= "cgroup",
206 		.revision	= 1,
207 		.family		= NFPROTO_UNSPEC,
208 		.checkentry	= cgroup_mt_check_v1,
209 		.match		= cgroup_mt_v1,
210 		.matchsize	= sizeof(struct xt_cgroup_info_v1),
211 		.usersize	= offsetof(struct xt_cgroup_info_v1, priv),
212 		.destroy	= cgroup_mt_destroy_v1,
213 		.me		= THIS_MODULE,
214 		.hooks		= (1 << NF_INET_LOCAL_OUT) |
215 				  (1 << NF_INET_POST_ROUTING) |
216 				  (1 << NF_INET_LOCAL_IN),
217 	},
218 	{
219 		.name		= "cgroup",
220 		.revision	= 2,
221 		.family		= NFPROTO_UNSPEC,
222 		.checkentry	= cgroup_mt_check_v2,
223 		.match		= cgroup_mt_v2,
224 		.matchsize	= sizeof(struct xt_cgroup_info_v2),
225 		.usersize	= offsetof(struct xt_cgroup_info_v2, priv),
226 		.destroy	= cgroup_mt_destroy_v2,
227 		.me		= THIS_MODULE,
228 		.hooks		= (1 << NF_INET_LOCAL_OUT) |
229 				  (1 << NF_INET_POST_ROUTING) |
230 				  (1 << NF_INET_LOCAL_IN),
231 	},
232 };
233 
234 static int __init cgroup_mt_init(void)
235 {
236 	return xt_register_matches(cgroup_mt_reg, ARRAY_SIZE(cgroup_mt_reg));
237 }
238 
239 static void __exit cgroup_mt_exit(void)
240 {
241 	xt_unregister_matches(cgroup_mt_reg, ARRAY_SIZE(cgroup_mt_reg));
242 }
243 
244 module_init(cgroup_mt_init);
245 module_exit(cgroup_mt_exit);
246