Vulnerability: In the Linux kernel before 4.20.12, net/ipv4/netfilter/nf_nat_snmp_basic_main.c in the SNMP NAT module has insufficient ASN.1 length checks (aka an array index error), making out-of-bounds read and write operations possible, leading to an OOPS or local privilege escalation. This affects snmp_version and snmp_helper.
Vulnerability ID: CVE-2019-9162
CWE: CWE-787
Severity score: 6.4
Project: https://github.com/torvalds/linux
Published: 2019-02-25
File: net/ipv4/netfilter/nf_nat_snmp_basic_main.c
Commit message: netfilter: nf_nat_snmp_basic: add missing length checks in ASN.1 cbs

The generic ASN.1 decoder infrastructure doesn't guarantee that callbacks
will get as much data as they expect; callbacks have to check the `datalen`
parameter before looking at `data`. Make sure that snmp_version() and
snmp_helper() don't read/write beyond the end of the packet data.

(Also move the assignment to `pdata` down below the check to make it clear
that it isn't necessarily a pointer we can use before the `datalen` check.)

Fixes: cc2d58634e0f ("netfilter: nf_nat_snmp_basic: use asn1 decoder library")
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Commit author: Jann Horn

Vulnerable version (before fix):
 static void fast_csum(struct snmp_ctx *ctx, unsigned char offset) int snmp_version(void *context, size_t hdrlen, unsigned char tag, 		 const void *data, size_t datalen) { 	if (*(unsigned char *)data > 1) 		return -ENOTSUPP; 	return 1; int snmp_helper(void *context, size_t hdrlen, unsigned char tag, 		const void *data, size_t datalen) { 	struct snmp_ctx *ctx = (struct snmp_ctx *)context; 	__be32 *pdata = (__be32 *)data;  	if (*pdata == ctx->from) { 		pr_debug("%s: %pI4 to %pI4\n", __func__, 			 (void *)&ctx->from, (void *)&ctx->to);

Fixed version (after fix):
 static void fast_csum(struct snmp_ctx *ctx, unsigned char offset) int snmp_version(void *context, size_t hdrlen, unsigned char tag, 		 const void *data, size_t datalen) { 	if (datalen != 1) 		return -EINVAL; 	if (*(unsigned char *)data > 1) 		return -ENOTSUPP; 	return 1; int snmp_helper(void *context, size_t hdrlen, unsigned char tag, 		const void *data, size_t datalen) { 	struct snmp_ctx *ctx = (struct snmp_ctx *)context; 	__be32 *pdata;  	if (datalen != 4) 		return -EINVAL; 	pdata = (__be32 *)data; 	if (*pdata == ctx->from) { 		pr_debug("%s: %pI4 to %pI4\n", __func__, 			 (void *)&ctx->from, (void *)&ctx->to);

Patch:
patch: @@ -105,6 +105,8 @@ static void fast_csum(struct snmp_ctx *ctx, unsigned char offset)
 int snmp_version(void *context, size_t hdrlen, unsigned char tag,
 		 const void *data, size_t datalen)
 {
+	if (datalen != 1)
+		return -EINVAL;
 	if (*(unsigned char *)data > 1)
 		return -ENOTSUPP;
 	return 1;
@@ -114,8 +116,11 @@ int snmp_helper(void *context, size_t hdrlen, unsigned char tag,
 		const void *data, size_t datalen)
 {
 	struct snmp_ctx *ctx = (struct snmp_ctx *)context;
-	__be32 *pdata = (__be32 *)data;
+	__be32 *pdata;
 
+	if (datalen != 4)
+		return -EINVAL;
+	pdata = (__be32 *)data;
 	if (*pdata == ctx->from) {
 		pr_debug("%s: %pI4 to %pI4\n", __func__,
 			 (void *)&ctx->from, (void *)&ctx->to);
path: net/ipv4/netfilter/nf_nat_snmp_basic_main.c