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 static int cgroup_mt_check_v0(const struct xt_mtchk_param *par) 27 { 28 struct xt_cgroup_info_v0 *info = par->matchinfo; 29 30 if (info->invert & ~1) 31 return -EINVAL; 32 33 return 0; 34 } 35 36 static int cgroup_mt_check_v1(const struct xt_mtchk_param *par) 37 { 38 struct xt_cgroup_info_v1 *info = par->matchinfo; 39 struct cgroup *cgrp; 40 41 if ((info->invert_path & ~1) || (info->invert_classid & ~1)) 42 return -EINVAL; 43 44 if (!info->has_path && !info->has_classid) { 45 pr_info("xt_cgroup: no path or classid specified\n"); 46 return -EINVAL; 47 } 48 49 if (info->has_path && info->has_classid) { 50 pr_info_ratelimited("path and classid specified\n"); 51 return -EINVAL; 52 } 53 54 info->priv = NULL; 55 if (info->has_path) { 56 cgrp = cgroup_get_from_path(info->path); 57 if (IS_ERR(cgrp)) { 58 pr_info_ratelimited("invalid path, errno=%ld\n", 59 PTR_ERR(cgrp)); 60 return -EINVAL; 61 } 62 info->priv = cgrp; 63 } 64 65 return 0; 66 } 67 68 static int cgroup_mt_check_v2(const struct xt_mtchk_param *par) 69 { 70 struct xt_cgroup_info_v2 *info = par->matchinfo; 71 struct cgroup *cgrp; 72 73 if ((info->invert_path & ~1) || (info->invert_classid & ~1)) 74 return -EINVAL; 75 76 if (!info->has_path && !info->has_classid) { 77 pr_info("xt_cgroup: no path or classid specified\n"); 78 return -EINVAL; 79 } 80 81 if (info->has_path && info->has_classid) { 82 pr_info_ratelimited("path and classid specified\n"); 83 return -EINVAL; 84 } 85 86 info->priv = NULL; 87 if (info->has_path) { 88 cgrp = cgroup_get_from_path(info->path); 89 if (IS_ERR(cgrp)) { 90 pr_info_ratelimited("invalid path, errno=%ld\n", 91 PTR_ERR(cgrp)); 92 return -EINVAL; 93 } 94 info->priv = cgrp; 95 } 96 97 return 0; 98 } 99 100 static bool 101 cgroup_mt_v0(const struct sk_buff *skb, struct xt_action_param *par) 102 { 103 const struct xt_cgroup_info_v0 *info = par->matchinfo; 104 struct sock *sk = skb->sk; 105 106 if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk))) 107 return false; 108 109 return (info->id == sock_cgroup_classid(&skb->sk->sk_cgrp_data)) ^ 110 info->invert; 111 } 112 113 static bool cgroup_mt_v1(const struct sk_buff *skb, struct xt_action_param *par) 114 { 115 const struct xt_cgroup_info_v1 *info = par->matchinfo; 116 struct sock_cgroup_data *skcd = &skb->sk->sk_cgrp_data; 117 struct cgroup *ancestor = info->priv; 118 struct sock *sk = skb->sk; 119 120 if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk))) 121 return false; 122 123 if (ancestor) 124 return cgroup_is_descendant(sock_cgroup_ptr(skcd), ancestor) ^ 125 info->invert_path; 126 else 127 return (info->classid == sock_cgroup_classid(skcd)) ^ 128 info->invert_classid; 129 } 130 131 static bool cgroup_mt_v2(const struct sk_buff *skb, struct xt_action_param *par) 132 { 133 const struct xt_cgroup_info_v2 *info = par->matchinfo; 134 struct sock_cgroup_data *skcd = &skb->sk->sk_cgrp_data; 135 struct cgroup *ancestor = info->priv; 136 struct sock *sk = skb->sk; 137 138 if (!sk || !sk_fullsock(sk) || !net_eq(xt_net(par), sock_net(sk))) 139 return false; 140 141 if (ancestor) 142 return cgroup_is_descendant(sock_cgroup_ptr(skcd), ancestor) ^ 143 info->invert_path; 144 else 145 return (info->classid == sock_cgroup_classid(skcd)) ^ 146 info->invert_classid; 147 } 148 149 static void cgroup_mt_destroy_v1(const struct xt_mtdtor_param *par) 150 { 151 struct xt_cgroup_info_v1 *info = par->matchinfo; 152 153 if (info->priv) 154 cgroup_put(info->priv); 155 } 156 157 static void cgroup_mt_destroy_v2(const struct xt_mtdtor_param *par) 158 { 159 struct xt_cgroup_info_v2 *info = par->matchinfo; 160 161 if (info->priv) 162 cgroup_put(info->priv); 163 } 164 165 static struct xt_match cgroup_mt_reg[] __read_mostly = { 166 { 167 .name = "cgroup", 168 .revision = 0, 169 .family = NFPROTO_UNSPEC, 170 .checkentry = cgroup_mt_check_v0, 171 .match = cgroup_mt_v0, 172 .matchsize = sizeof(struct xt_cgroup_info_v0), 173 .me = THIS_MODULE, 174 .hooks = (1 << NF_INET_LOCAL_OUT) | 175 (1 << NF_INET_POST_ROUTING) | 176 (1 << NF_INET_LOCAL_IN), 177 }, 178 { 179 .name = "cgroup", 180 .revision = 1, 181 .family = NFPROTO_UNSPEC, 182 .checkentry = cgroup_mt_check_v1, 183 .match = cgroup_mt_v1, 184 .matchsize = sizeof(struct xt_cgroup_info_v1), 185 .usersize = offsetof(struct xt_cgroup_info_v1, priv), 186 .destroy = cgroup_mt_destroy_v1, 187 .me = THIS_MODULE, 188 .hooks = (1 << NF_INET_LOCAL_OUT) | 189 (1 << NF_INET_POST_ROUTING) | 190 (1 << NF_INET_LOCAL_IN), 191 }, 192 { 193 .name = "cgroup", 194 .revision = 2, 195 .family = NFPROTO_UNSPEC, 196 .checkentry = cgroup_mt_check_v2, 197 .match = cgroup_mt_v2, 198 .matchsize = sizeof(struct xt_cgroup_info_v2), 199 .usersize = offsetof(struct xt_cgroup_info_v2, priv), 200 .destroy = cgroup_mt_destroy_v2, 201 .me = THIS_MODULE, 202 .hooks = (1 << NF_INET_LOCAL_OUT) | 203 (1 << NF_INET_POST_ROUTING) | 204 (1 << NF_INET_LOCAL_IN), 205 }, 206 }; 207 208 static int __init cgroup_mt_init(void) 209 { 210 return xt_register_matches(cgroup_mt_reg, ARRAY_SIZE(cgroup_mt_reg)); 211 } 212 213 static void __exit cgroup_mt_exit(void) 214 { 215 xt_unregister_matches(cgroup_mt_reg, ARRAY_SIZE(cgroup_mt_reg)); 216 } 217 218 module_init(cgroup_mt_init); 219 module_exit(cgroup_mt_exit); 220