mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
netfilter: nftables: restrict checkum update offset
After previous patch, writes to network header are restricted. However, there is another way to manipulate the l3 header: The checksum update function. Restrict this for network header writes, only the ipv4 header is allowed. This needs run-time checks because BRIDGE, INET, NETDEV families can carry l3 headers other than IP. checksum updates to the udp/tcp (l4) headers are not restricted. Signed-off-by: Florian Westphal <fw@strlen.de>
This commit is contained in:
@@ -1000,6 +1000,83 @@ static bool nft_ll_write_ok(const struct nft_pktinfo *pkt, int offset)
|
||||
return offset <= skb_network_offset(pkt->skb);
|
||||
}
|
||||
|
||||
static bool nft_payload_validate_inet_csum_offset(const struct nft_ctx *ctx,
|
||||
const struct nft_payload_set *priv)
|
||||
{
|
||||
switch (priv->base) {
|
||||
case NFT_PAYLOAD_LL_HEADER:
|
||||
break;
|
||||
case NFT_PAYLOAD_NETWORK_HEADER:
|
||||
if (ctx->family == NFPROTO_IPV4) {
|
||||
if (offsetof(struct iphdr, check) == priv->csum_offset)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
return true; /* run time validation required */
|
||||
case NFT_PAYLOAD_TRANSPORT_HEADER:
|
||||
if (priv->csum_flags) /* makes no sense, asks for "re-update" of L4 checksum */
|
||||
return false;
|
||||
|
||||
/* no further check here; offset can't be negative so bogus
|
||||
* offsets can corrupt L4 or payload but not l3 headers.
|
||||
* We already allow arbitrary l4/inner payload writes.
|
||||
*/
|
||||
return true;
|
||||
case NFT_PAYLOAD_INNER_HEADER:
|
||||
return true;
|
||||
case NFT_PAYLOAD_TUN_HEADER:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* do not allow arbitrary network header mangling via bogus csum_off.
|
||||
* We only support ipv4. Only NFPROTO_IPV4 can be checked from control
|
||||
* plane.
|
||||
*/
|
||||
static bool nft_payload_csum_nh_write_ok(const struct nft_payload_set *priv,
|
||||
const struct nft_pktinfo *pkt)
|
||||
{
|
||||
switch (pkt->state->pf) {
|
||||
case NFPROTO_IPV4:
|
||||
/* Warning: NFPROTO_INET was not checked; we can't return true here. */
|
||||
return priv->csum_offset == offsetof(struct iphdr, check);
|
||||
case NFPROTO_IPV6:
|
||||
return false;
|
||||
case NFPROTO_BRIDGE:
|
||||
return pkt->ethertype == htons(ETH_P_IP) &&
|
||||
priv->csum_offset == offsetof(struct iphdr, check);
|
||||
case NFPROTO_NETDEV:
|
||||
return pkt->skb->protocol == htons(ETH_P_IP) &&
|
||||
priv->csum_offset == offsetof(struct iphdr, check);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
|
||||
const struct nft_payload_set *priv)
|
||||
{
|
||||
switch (priv->base) {
|
||||
case NFT_PAYLOAD_LL_HEADER:
|
||||
break;
|
||||
case NFT_PAYLOAD_NETWORK_HEADER:
|
||||
return nft_payload_csum_nh_write_ok(priv, pkt);
|
||||
case NFT_PAYLOAD_TRANSPORT_HEADER:
|
||||
case NFT_PAYLOAD_INNER_HEADER:
|
||||
/* neither offsets are validated, offsets cannot be
|
||||
* negative so real l3 headers cannot be mangled.
|
||||
*/
|
||||
return true;
|
||||
case NFT_PAYLOAD_TUN_HEADER:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nft_payload_set_eval(const struct nft_expr *expr,
|
||||
struct nft_regs *regs,
|
||||
const struct nft_pktinfo *pkt)
|
||||
@@ -1064,6 +1141,7 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
|
||||
tsum = csum_partial(src, priv->len, 0);
|
||||
|
||||
if (priv->csum_type == NFT_PAYLOAD_CSUM_INET &&
|
||||
nft_payload_csum_write_ok(pkt, priv) &&
|
||||
nft_payload_csum_inet(skb, src, fsum, tsum, csum_offset))
|
||||
goto err;
|
||||
|
||||
@@ -1130,7 +1208,26 @@ static int nft_payload_set_init(const struct nft_ctx *ctx,
|
||||
|
||||
switch (csum_type) {
|
||||
case NFT_PAYLOAD_CSUM_NONE:
|
||||
if (priv->csum_offset) /* nonsensical */
|
||||
return -EINVAL;
|
||||
|
||||
if (priv->csum_flags == 0)
|
||||
break;
|
||||
|
||||
/* Userspace requests L4 checksum update, e.g.:
|
||||
* - IPv6 stateless NAT (no l3 csum)
|
||||
* - transport header mangling
|
||||
* - inner data mangling
|
||||
*/
|
||||
if (priv->base == NFT_PAYLOAD_NETWORK_HEADER ||
|
||||
priv->base == NFT_PAYLOAD_TRANSPORT_HEADER ||
|
||||
priv->base == NFT_PAYLOAD_INNER_HEADER)
|
||||
break;
|
||||
|
||||
return -EINVAL;
|
||||
case NFT_PAYLOAD_CSUM_INET:
|
||||
if (!nft_payload_validate_inet_csum_offset(ctx, priv))
|
||||
return -EINVAL;
|
||||
break;
|
||||
case NFT_PAYLOAD_CSUM_SCTP:
|
||||
if (priv->base != NFT_PAYLOAD_TRANSPORT_HEADER)
|
||||
@@ -1138,6 +1235,9 @@ static int nft_payload_set_init(const struct nft_ctx *ctx,
|
||||
|
||||
if (priv->csum_offset != offsetof(struct sctphdr, checksum))
|
||||
return -EINVAL;
|
||||
|
||||
if (priv->csum_flags)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
Reference in New Issue
Block a user