| 1 | // SPDX-License-Identifier: GPL-2.0-only | 
|---|
| 2 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|---|
| 3 | #include <linux/types.h> | 
|---|
| 4 | #include <linux/module.h> | 
|---|
| 5 | #include <net/ip.h> | 
|---|
| 6 | #include <linux/ipv6.h> | 
|---|
| 7 | #include <linux/icmp.h> | 
|---|
| 8 | #include <net/ipv6.h> | 
|---|
| 9 | #include <net/tcp.h> | 
|---|
| 10 | #include <net/udp.h> | 
|---|
| 11 | #include <linux/netfilter/x_tables.h> | 
|---|
| 12 | #include <linux/netfilter/xt_tcpudp.h> | 
|---|
| 13 | #include <linux/netfilter_ipv4/ip_tables.h> | 
|---|
| 14 | #include <linux/netfilter_ipv6/ip6_tables.h> | 
|---|
| 15 |  | 
|---|
| 16 | MODULE_DESCRIPTION( "Xtables: TCP, UDP and UDP-Lite match"); | 
|---|
| 17 | MODULE_LICENSE( "GPL"); | 
|---|
| 18 | MODULE_ALIAS( "xt_tcp"); | 
|---|
| 19 | MODULE_ALIAS( "xt_udp"); | 
|---|
| 20 | MODULE_ALIAS( "ipt_udp"); | 
|---|
| 21 | MODULE_ALIAS( "ipt_tcp"); | 
|---|
| 22 | MODULE_ALIAS( "ip6t_udp"); | 
|---|
| 23 | MODULE_ALIAS( "ip6t_tcp"); | 
|---|
| 24 | MODULE_ALIAS( "ipt_icmp"); | 
|---|
| 25 | MODULE_ALIAS( "ip6t_icmp6"); | 
|---|
| 26 |  | 
|---|
| 27 | /* Returns 1 if the port is matched by the range, 0 otherwise */ | 
|---|
| 28 | static inline bool | 
|---|
| 29 | port_match(u_int16_t min, u_int16_t max, u_int16_t port, bool invert) | 
|---|
| 30 | { | 
|---|
| 31 | return (port >= min && port <= max) ^ invert; | 
|---|
| 32 | } | 
|---|
| 33 |  | 
|---|
| 34 | static bool | 
|---|
| 35 | tcp_find_option(u_int8_t option, | 
|---|
| 36 | const struct sk_buff *skb, | 
|---|
| 37 | unsigned int protoff, | 
|---|
| 38 | unsigned int optlen, | 
|---|
| 39 | bool invert, | 
|---|
| 40 | bool *hotdrop) | 
|---|
| 41 | { | 
|---|
| 42 | /* tcp.doff is only 4 bits, ie. max 15 * 4 bytes */ | 
|---|
| 43 | const u_int8_t *op; | 
|---|
| 44 | u_int8_t _opt[60 - sizeof(struct tcphdr)]; | 
|---|
| 45 | unsigned int i; | 
|---|
| 46 |  | 
|---|
| 47 | pr_debug( "finding option\n"); | 
|---|
| 48 |  | 
|---|
| 49 | if (!optlen) | 
|---|
| 50 | return invert; | 
|---|
| 51 |  | 
|---|
| 52 | /* If we don't have the whole header, drop packet. */ | 
|---|
| 53 | op = skb_header_pointer(skb, offset: protoff + sizeof(struct tcphdr), | 
|---|
| 54 | len: optlen, buffer: _opt); | 
|---|
| 55 | if (op == NULL) { | 
|---|
| 56 | *hotdrop = true; | 
|---|
| 57 | return false; | 
|---|
| 58 | } | 
|---|
| 59 |  | 
|---|
| 60 | for (i = 0; i < optlen; ) { | 
|---|
| 61 | if (op[i] == option) return !invert; | 
|---|
| 62 | if (op[i] < 2) i++; | 
|---|
| 63 | else i += op[i+1]?:1; | 
|---|
| 64 | } | 
|---|
| 65 |  | 
|---|
| 66 | return invert; | 
|---|
| 67 | } | 
|---|
| 68 |  | 
|---|
| 69 | static bool tcp_mt(const struct sk_buff *skb, struct xt_action_param *par) | 
|---|
| 70 | { | 
|---|
| 71 | const struct tcphdr *th; | 
|---|
| 72 | struct tcphdr _tcph; | 
|---|
| 73 | const struct xt_tcp *tcpinfo = par->matchinfo; | 
|---|
| 74 |  | 
|---|
| 75 | if (par->fragoff != 0) { | 
|---|
| 76 | /* To quote Alan: | 
|---|
| 77 |  | 
|---|
| 78 | Don't allow a fragment of TCP 8 bytes in. Nobody normal | 
|---|
| 79 | causes this. Its a cracker trying to break in by doing a | 
|---|
| 80 | flag overwrite to pass the direction checks. | 
|---|
| 81 | */ | 
|---|
| 82 | if (par->fragoff == 1) { | 
|---|
| 83 | pr_debug( "Dropping evil TCP offset=1 frag.\n"); | 
|---|
| 84 | par->hotdrop = true; | 
|---|
| 85 | } | 
|---|
| 86 | /* Must not be a fragment. */ | 
|---|
| 87 | return false; | 
|---|
| 88 | } | 
|---|
| 89 |  | 
|---|
| 90 | th = skb_header_pointer(skb, offset: par->thoff, len: sizeof(_tcph), buffer: &_tcph); | 
|---|
| 91 | if (th == NULL) { | 
|---|
| 92 | /* We've been asked to examine this packet, and we | 
|---|
| 93 | can't.  Hence, no choice but to drop. */ | 
|---|
| 94 | pr_debug( "Dropping evil TCP offset=0 tinygram.\n"); | 
|---|
| 95 | par->hotdrop = true; | 
|---|
| 96 | return false; | 
|---|
| 97 | } | 
|---|
| 98 |  | 
|---|
| 99 | if (!port_match(min: tcpinfo->spts[0], max: tcpinfo->spts[1], | 
|---|
| 100 | ntohs(th->source), | 
|---|
| 101 | invert: !!(tcpinfo->invflags & XT_TCP_INV_SRCPT))) | 
|---|
| 102 | return false; | 
|---|
| 103 | if (!port_match(min: tcpinfo->dpts[0], max: tcpinfo->dpts[1], | 
|---|
| 104 | ntohs(th->dest), | 
|---|
| 105 | invert: !!(tcpinfo->invflags & XT_TCP_INV_DSTPT))) | 
|---|
| 106 | return false; | 
|---|
| 107 | if (!NF_INVF(tcpinfo, XT_TCP_INV_FLAGS, | 
|---|
| 108 | (((unsigned char *)th)[13] & tcpinfo->flg_mask) == tcpinfo->flg_cmp)) | 
|---|
| 109 | return false; | 
|---|
| 110 | if (tcpinfo->option) { | 
|---|
| 111 | if (th->doff * 4 < sizeof(_tcph)) { | 
|---|
| 112 | par->hotdrop = true; | 
|---|
| 113 | return false; | 
|---|
| 114 | } | 
|---|
| 115 | if (!tcp_find_option(option: tcpinfo->option, skb, protoff: par->thoff, | 
|---|
| 116 | optlen: th->doff*4 - sizeof(_tcph), | 
|---|
| 117 | invert: tcpinfo->invflags & XT_TCP_INV_OPTION, | 
|---|
| 118 | hotdrop: &par->hotdrop)) | 
|---|
| 119 | return false; | 
|---|
| 120 | } | 
|---|
| 121 | return true; | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | static int tcp_mt_check(const struct xt_mtchk_param *par) | 
|---|
| 125 | { | 
|---|
| 126 | const struct xt_tcp *tcpinfo = par->matchinfo; | 
|---|
| 127 |  | 
|---|
| 128 | /* Must specify no unknown invflags */ | 
|---|
| 129 | return (tcpinfo->invflags & ~XT_TCP_INV_MASK) ? -EINVAL : 0; | 
|---|
| 130 | } | 
|---|
| 131 |  | 
|---|
| 132 | static bool udp_mt(const struct sk_buff *skb, struct xt_action_param *par) | 
|---|
| 133 | { | 
|---|
| 134 | const struct udphdr *uh; | 
|---|
| 135 | struct udphdr _udph; | 
|---|
| 136 | const struct xt_udp *udpinfo = par->matchinfo; | 
|---|
| 137 |  | 
|---|
| 138 | /* Must not be a fragment. */ | 
|---|
| 139 | if (par->fragoff != 0) | 
|---|
| 140 | return false; | 
|---|
| 141 |  | 
|---|
| 142 | uh = skb_header_pointer(skb, offset: par->thoff, len: sizeof(_udph), buffer: &_udph); | 
|---|
| 143 | if (uh == NULL) { | 
|---|
| 144 | /* We've been asked to examine this packet, and we | 
|---|
| 145 | can't.  Hence, no choice but to drop. */ | 
|---|
| 146 | pr_debug( "Dropping evil UDP tinygram.\n"); | 
|---|
| 147 | par->hotdrop = true; | 
|---|
| 148 | return false; | 
|---|
| 149 | } | 
|---|
| 150 |  | 
|---|
| 151 | return port_match(min: udpinfo->spts[0], max: udpinfo->spts[1], | 
|---|
| 152 | ntohs(uh->source), | 
|---|
| 153 | invert: !!(udpinfo->invflags & XT_UDP_INV_SRCPT)) | 
|---|
| 154 | && port_match(min: udpinfo->dpts[0], max: udpinfo->dpts[1], | 
|---|
| 155 | ntohs(uh->dest), | 
|---|
| 156 | invert: !!(udpinfo->invflags & XT_UDP_INV_DSTPT)); | 
|---|
| 157 | } | 
|---|
| 158 |  | 
|---|
| 159 | static int udp_mt_check(const struct xt_mtchk_param *par) | 
|---|
| 160 | { | 
|---|
| 161 | const struct xt_udp *udpinfo = par->matchinfo; | 
|---|
| 162 |  | 
|---|
| 163 | /* Must specify no unknown invflags */ | 
|---|
| 164 | return (udpinfo->invflags & ~XT_UDP_INV_MASK) ? -EINVAL : 0; | 
|---|
| 165 | } | 
|---|
| 166 |  | 
|---|
| 167 | /* Returns 1 if the type and code is matched by the range, 0 otherwise */ | 
|---|
| 168 | static bool type_code_in_range(u8 test_type, u8 min_code, u8 max_code, | 
|---|
| 169 | u8 type, u8 code) | 
|---|
| 170 | { | 
|---|
| 171 | return type == test_type && code >= min_code && code <= max_code; | 
|---|
| 172 | } | 
|---|
| 173 |  | 
|---|
| 174 | static bool icmp_type_code_match(u8 test_type, u8 min_code, u8 max_code, | 
|---|
| 175 | u8 type, u8 code, bool invert) | 
|---|
| 176 | { | 
|---|
| 177 | return (test_type == 0xFF || | 
|---|
| 178 | type_code_in_range(test_type, min_code, max_code, type, code)) | 
|---|
| 179 | ^ invert; | 
|---|
| 180 | } | 
|---|
| 181 |  | 
|---|
| 182 | static bool icmp6_type_code_match(u8 test_type, u8 min_code, u8 max_code, | 
|---|
| 183 | u8 type, u8 code, bool invert) | 
|---|
| 184 | { | 
|---|
| 185 | return type_code_in_range(test_type, min_code, max_code, type, code) ^ invert; | 
|---|
| 186 | } | 
|---|
| 187 |  | 
|---|
| 188 | static bool | 
|---|
| 189 | icmp_match(const struct sk_buff *skb, struct xt_action_param *par) | 
|---|
| 190 | { | 
|---|
| 191 | const struct icmphdr *ic; | 
|---|
| 192 | struct icmphdr _icmph; | 
|---|
| 193 | const struct ipt_icmp *icmpinfo = par->matchinfo; | 
|---|
| 194 |  | 
|---|
| 195 | /* Must not be a fragment. */ | 
|---|
| 196 | if (par->fragoff != 0) | 
|---|
| 197 | return false; | 
|---|
| 198 |  | 
|---|
| 199 | ic = skb_header_pointer(skb, offset: par->thoff, len: sizeof(_icmph), buffer: &_icmph); | 
|---|
| 200 | if (!ic) { | 
|---|
| 201 | /* We've been asked to examine this packet, and we | 
|---|
| 202 | * can't.  Hence, no choice but to drop. | 
|---|
| 203 | */ | 
|---|
| 204 | par->hotdrop = true; | 
|---|
| 205 | return false; | 
|---|
| 206 | } | 
|---|
| 207 |  | 
|---|
| 208 | return icmp_type_code_match(test_type: icmpinfo->type, | 
|---|
| 209 | min_code: icmpinfo->code[0], | 
|---|
| 210 | max_code: icmpinfo->code[1], | 
|---|
| 211 | type: ic->type, code: ic->code, | 
|---|
| 212 | invert: !!(icmpinfo->invflags & IPT_ICMP_INV)); | 
|---|
| 213 | } | 
|---|
| 214 |  | 
|---|
| 215 | static bool | 
|---|
| 216 | icmp6_match(const struct sk_buff *skb, struct xt_action_param *par) | 
|---|
| 217 | { | 
|---|
| 218 | const struct icmp6hdr *ic; | 
|---|
| 219 | struct icmp6hdr _icmph; | 
|---|
| 220 | const struct ip6t_icmp *icmpinfo = par->matchinfo; | 
|---|
| 221 |  | 
|---|
| 222 | /* Must not be a fragment. */ | 
|---|
| 223 | if (par->fragoff != 0) | 
|---|
| 224 | return false; | 
|---|
| 225 |  | 
|---|
| 226 | ic = skb_header_pointer(skb, offset: par->thoff, len: sizeof(_icmph), buffer: &_icmph); | 
|---|
| 227 | if (!ic) { | 
|---|
| 228 | /* We've been asked to examine this packet, and we | 
|---|
| 229 | * can't.  Hence, no choice but to drop. | 
|---|
| 230 | */ | 
|---|
| 231 | par->hotdrop = true; | 
|---|
| 232 | return false; | 
|---|
| 233 | } | 
|---|
| 234 |  | 
|---|
| 235 | return icmp6_type_code_match(test_type: icmpinfo->type, | 
|---|
| 236 | min_code: icmpinfo->code[0], | 
|---|
| 237 | max_code: icmpinfo->code[1], | 
|---|
| 238 | type: ic->icmp6_type, code: ic->icmp6_code, | 
|---|
| 239 | invert: !!(icmpinfo->invflags & IP6T_ICMP_INV)); | 
|---|
| 240 | } | 
|---|
| 241 |  | 
|---|
| 242 | static int icmp_checkentry(const struct xt_mtchk_param *par) | 
|---|
| 243 | { | 
|---|
| 244 | const struct ipt_icmp *icmpinfo = par->matchinfo; | 
|---|
| 245 |  | 
|---|
| 246 | return (icmpinfo->invflags & ~IPT_ICMP_INV) ? -EINVAL : 0; | 
|---|
| 247 | } | 
|---|
| 248 |  | 
|---|
| 249 | static int icmp6_checkentry(const struct xt_mtchk_param *par) | 
|---|
| 250 | { | 
|---|
| 251 | const struct ip6t_icmp *icmpinfo = par->matchinfo; | 
|---|
| 252 |  | 
|---|
| 253 | return (icmpinfo->invflags & ~IP6T_ICMP_INV) ? -EINVAL : 0; | 
|---|
| 254 | } | 
|---|
| 255 |  | 
|---|
| 256 | static struct xt_match tcpudp_mt_reg[] __read_mostly = { | 
|---|
| 257 | { | 
|---|
| 258 | .name		= "tcp", | 
|---|
| 259 | .family		= NFPROTO_IPV4, | 
|---|
| 260 | .checkentry	= tcp_mt_check, | 
|---|
| 261 | .match		= tcp_mt, | 
|---|
| 262 | .matchsize	= sizeof(struct xt_tcp), | 
|---|
| 263 | .proto		= IPPROTO_TCP, | 
|---|
| 264 | .me		= THIS_MODULE, | 
|---|
| 265 | }, | 
|---|
| 266 | { | 
|---|
| 267 | .name		= "tcp", | 
|---|
| 268 | .family		= NFPROTO_IPV6, | 
|---|
| 269 | .checkentry	= tcp_mt_check, | 
|---|
| 270 | .match		= tcp_mt, | 
|---|
| 271 | .matchsize	= sizeof(struct xt_tcp), | 
|---|
| 272 | .proto		= IPPROTO_TCP, | 
|---|
| 273 | .me		= THIS_MODULE, | 
|---|
| 274 | }, | 
|---|
| 275 | { | 
|---|
| 276 | .name		= "udp", | 
|---|
| 277 | .family		= NFPROTO_IPV4, | 
|---|
| 278 | .checkentry	= udp_mt_check, | 
|---|
| 279 | .match		= udp_mt, | 
|---|
| 280 | .matchsize	= sizeof(struct xt_udp), | 
|---|
| 281 | .proto		= IPPROTO_UDP, | 
|---|
| 282 | .me		= THIS_MODULE, | 
|---|
| 283 | }, | 
|---|
| 284 | { | 
|---|
| 285 | .name		= "udp", | 
|---|
| 286 | .family		= NFPROTO_IPV6, | 
|---|
| 287 | .checkentry	= udp_mt_check, | 
|---|
| 288 | .match		= udp_mt, | 
|---|
| 289 | .matchsize	= sizeof(struct xt_udp), | 
|---|
| 290 | .proto		= IPPROTO_UDP, | 
|---|
| 291 | .me		= THIS_MODULE, | 
|---|
| 292 | }, | 
|---|
| 293 | { | 
|---|
| 294 | .name		= "udplite", | 
|---|
| 295 | .family		= NFPROTO_IPV4, | 
|---|
| 296 | .checkentry	= udp_mt_check, | 
|---|
| 297 | .match		= udp_mt, | 
|---|
| 298 | .matchsize	= sizeof(struct xt_udp), | 
|---|
| 299 | .proto		= IPPROTO_UDPLITE, | 
|---|
| 300 | .me		= THIS_MODULE, | 
|---|
| 301 | }, | 
|---|
| 302 | { | 
|---|
| 303 | .name		= "udplite", | 
|---|
| 304 | .family		= NFPROTO_IPV6, | 
|---|
| 305 | .checkentry	= udp_mt_check, | 
|---|
| 306 | .match		= udp_mt, | 
|---|
| 307 | .matchsize	= sizeof(struct xt_udp), | 
|---|
| 308 | .proto		= IPPROTO_UDPLITE, | 
|---|
| 309 | .me		= THIS_MODULE, | 
|---|
| 310 | }, | 
|---|
| 311 | { | 
|---|
| 312 | .name       = "icmp", | 
|---|
| 313 | .match      = icmp_match, | 
|---|
| 314 | .matchsize  = sizeof(struct ipt_icmp), | 
|---|
| 315 | .checkentry = icmp_checkentry, | 
|---|
| 316 | .proto      = IPPROTO_ICMP, | 
|---|
| 317 | .family     = NFPROTO_IPV4, | 
|---|
| 318 | .me         = THIS_MODULE, | 
|---|
| 319 | }, | 
|---|
| 320 | { | 
|---|
| 321 | .name       = "icmp6", | 
|---|
| 322 | .match      = icmp6_match, | 
|---|
| 323 | .matchsize  = sizeof(struct ip6t_icmp), | 
|---|
| 324 | .checkentry = icmp6_checkentry, | 
|---|
| 325 | .proto      = IPPROTO_ICMPV6, | 
|---|
| 326 | .family     = NFPROTO_IPV6, | 
|---|
| 327 | .me	    = THIS_MODULE, | 
|---|
| 328 | }, | 
|---|
| 329 | }; | 
|---|
| 330 |  | 
|---|
| 331 | static int __init tcpudp_mt_init(void) | 
|---|
| 332 | { | 
|---|
| 333 | return xt_register_matches(match: tcpudp_mt_reg, ARRAY_SIZE(tcpudp_mt_reg)); | 
|---|
| 334 | } | 
|---|
| 335 |  | 
|---|
| 336 | static void __exit tcpudp_mt_exit(void) | 
|---|
| 337 | { | 
|---|
| 338 | xt_unregister_matches(match: tcpudp_mt_reg, ARRAY_SIZE(tcpudp_mt_reg)); | 
|---|
| 339 | } | 
|---|
| 340 |  | 
|---|
| 341 | module_init(tcpudp_mt_init); | 
|---|
| 342 | module_exit(tcpudp_mt_exit); | 
|---|
| 343 |  | 
|---|