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)
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;
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)
if (priv->csum_offset != offsetof(struct sctphdr, checksum))
return -EINVAL;
+
+ if (priv->csum_flags)
+ return -EINVAL;
break;
default:
return -EOPNOTSUPP;