xorl %eax, %eax

Archive for the ‘vulnerabilities’ Category

Overview of 0days seen in the wild the last 7 years

leave a comment »

Starting in 2019 Google’s Project Zero (P0) team published a tracker for all 0days that were discovered to be exploited in-the-wild (either by Google or others). The data from this tracker start from August 2014 and continue to this day (for this post that is October 2021).

Using this data source, I created some simple graphs to better understand the 0day threat landscape for all of us tasked with cybersecurity responsibilities.

There are however a couple of assumptions I’m making by using this data set, those assumptions are:

  • This is a representative sample of all the 0days used in the wild
  • The data collected in this tracker are accurate

So, let’s start first of all with the trend which is one of least useful graphs but the one people are regularly interested in. The reason why this is generally useless is because the dates are the dates when those 0days were officially patched, not when they were initially acquired by the threat actors, or their first operational use. Additionally, those patching dates rely on the cybersecurity industry and community actively working to discover and remediate 0days in the wild. So, there might have been periods that those companies/people had different priorities and didn’t dedicate as much time on threat hunting and remediation for 0days.

For all those reasons, I do not consider the following one particularly useful but added it here for completeness. If there was any conclusion someone could make from this is that the end of the year and early winter is consistently the period were the least amount of 0day discoveries and mitigations happen.

A far more interesting statistic is which are the top targets. That can help us focus on what is more likely to get hit with 0days in the future. Looking at the statistics for the last 7 years (2014-2021) it is clear that by far the top target is Microsoft (50%), followed by Adobe (13.9%), Google (11.9%), and then Apple (10.3%).

Later in this post I’ll go into more details on the each of the products most commonly targeted on those vendors, which can help us drive our priorities (e.g. where to hunt for more, what to protect more, which are the highest risk vendors) or even understand what most threat actors are interested in acquiring.

The second high-level overview graph that is particularly interesting is the type of vulnerability exploited where we see that 71.1% was memory corruption, and 16% was a logic/design flaw. This is another interesting metric since it can help us prioritize our efforts in those issues.

Interestingly, those are also typically some of the hardest to thoroughly audit (especially in an automated manner) and this might also be a factor on why they were the most frequently used. I mean the combination of being hard to discover vulnerability via the automated means most vendors are using, and the fact that they typically enable a wide variety of exploitation avenues.

Now in the next part I’ll be going through each of the top 5 vendors affected by 0days in the wild and see which of their products were targeted the most. Again, to help us prioritize our efforts accordingly.

#1 Microsoft (50% of all discovered 0days were for this vendor)

  • Windows (46.4%)
  • Internet Explorer (22.7%)
  • Office (13.4%)
  • Windows kernel (8.2%)
  • All the rest: Exchange, VBScript, XML Core Services, Defender (9.2%)

#2 Adobe (13.% of all discovered 0days were for this vendor)

  • Flash (85.2%)
  • Reader (14.8%)

#3 Google (11.9% of all discovered 0days were for this vendor)

  • Chrome (95.7%)
  • Android (4.3%)

#4 Apple (10.3% of all discovered 0days were for this vendor)

  • iOS (55%)
  • WebKit (40%)
  • MacOS (5%)

#5 Mozilla (3.6% of all discovered 0days were for this vendor)

  • Firefox (100%)

The final graph that I found particularly useful to understand the threat landscape is the average patching time per each of those top 5 affected vendors. For this one, please note that the Google tracker does not have a data for all entries. Specifically, 94.85% of the entries include both dates (discovery and patching) so this is what was used for the following calculations.

In total the average patching time was 22.67 days from the discovery time to the patch being publicly available, but below you can also see this metric per vendor.

The average patching time (from discovery to patch being publicly available) for each of the top 5 affected vendors are:

  1. Microsoft: Average of 41.2 days between discovery and patching
  2. Adobe: Average of 8.1 days between discovery and patching
  3. Google: Average of 5.3 days between discovery and patching
  4. Apple: Average of 9 days between discovery and patching
  5. Mozilla: Average of 6.2 days between discovery and patching

Based on the above data, Microsoft is the highest risk vendor if we combine the amount of 0days found in the wild and the average patching time of 41 days. So, maybe your strategy should involve minimizing the use of those products and services, or spending more resources in security controls around them.

Another valuable insight that we can derive is that the most targeted software by adversaries that employ 0day exploits are web browsers and mobile phones. Especially for the latter (mobile phones), it’s an area where most organizations do not pay sufficient attention to secure them, at least not the same level as their core infrastructure services.

I’m certain there are many more assessments that can be made using the above data but hopefully that gives you a starting point and a source of inspiration on how to get strategic value from tactical information such as 0day exploits discovered in the wild.

Written by xorl

October 19, 2021 at 16:26

CVE-2018-0739: OpenSSL ASN.1 stack overflow

leave a comment »

This was a vulnerability discovered by Google’s OSS-Fuzz project and it was fixed by Matt Caswell of the OpenSSL development team. The vulnerability affects OpenSSL releases prior to 1.0.2o and 1.1.0h and based on OpenSSL team’s assessment, this cannot be triggered via SSL/TLS but constructed ASN.1 types with support for recursive definitions, such as PKCS7 can be used to trigger it.

/*
 * Decode an item, taking care of IMPLICIT tagging, if any. If 'opt' set and
 * tag mismatch return -1 to handle OPTIONAL
 */

static int asn1_item_embed_d2i(ASN1_VALUE **pval, const unsigned char **in,
                               long len, const ASN1_ITEM *it,
                               int tag, int aclass, char opt, ASN1_TLC *ctx)
{
    const ASN1_TEMPLATE *tt, *errtt = NULL;
    const ASN1_EXTERN_FUNCS *ef;
    const ASN1_AUX *aux = it->funcs;
    ASN1_aux_cb *asn1_cb;
    const unsigned char *p = NULL, *q;
    unsigned char oclass;
    char seq_eoc, seq_nolen, cst, isopt;
    long tmplen;
    int i;
    int otag;
    int ret = 0;
    ASN1_VALUE **pchptr;
    if (!pval)
        return 0;
    if (aux && aux->asn1_cb)
        asn1_cb = aux->asn1_cb;
    else
        asn1_cb = 0;

    switch (it->itype) {
   ...
    return 0;
}

What you see above is a snippet of crypto/asn1/tasn_dec.c where the decoding ASN.1 function asn1_item_embed_d2i() is located. Neither this function nor any of its callers had any check for recursive definitions. This means that given a malicious PKCS7 file this decoding routine will keep on trying to decode them until the process will run out of stack space.

To fix this, a new error case was added in crypto/asn1/asn1.h header file named ASN1_R_NESTED_TOO_DEEP. If we have a look at crypto/asn1/asn1_err.c we can see that the new error code is equivalent to the “nested too deep” error message.

     {ERR_REASON(ASN1_R_NESTED_ASN1_STRING), "nested asn1 string"},
+    {ERR_REASON(ASN1_R_NESTED_TOO_DEEP), "nested too deep"},
     {ERR_REASON(ASN1_R_NON_HEX_CHARACTERS), "non hex characters"},

Similarly, a new constant (ASN1_MAX_CONSTRUCTED_NEST) definition was added which is used to define the maximum amount of recursive invocations of asn1_item_embed_d2i() function. You can see the new definition in crypto/asn1/tasn_dec.c.

 #include <openssl/err.h>

/*
 * Constructed types with a recursive definition (such as can be found in PKCS7)
 * could eventually exceed the stack given malicious input with excessive
 * recursion. Therefore we limit the stack depth. This is the maximum number of
 * recursive invocations of asn1_item_embed_d2i().
 */
#define ASN1_MAX_CONSTRUCTED_NEST 30

 static int asn1_check_eoc(const unsigned char **in, long len);

Lastly, the asn1_item_embed_d2i() function itself was modified to have a new integer argument “depth” which is used as a counter for each iteration. You can see how check is performed before entering the switch clause here.

        asn1_cb = 0; 

    if (++depth > ASN1_MAX_CONSTRUCTED_NEST) { 
        ASN1err(ASN1_F_ASN1_ITEM_EMBED_D2I, ASN1_R_NESTED_TOO_DEEP);
        goto err;
    }

    switch (it->itype) {
    case ASN1_ITYPE_PRIMITIVE:

Similarly, all calling functions on OpenSSL have been updated to ensure that the new argument is used as intended. The official security advisory describes the above vulnerability like this.

Constructed ASN.1 types with a recursive definition could exceed the stack (CVE-2018-0739)
==========================================================================================

Severity: Moderate

Constructed ASN.1 types with a recursive definition (such as can be found in
PKCS7) could eventually exceed the stack given malicious input with
excessive recursion. This could result in a Denial Of Service attack. There are
no such structures used within SSL/TLS that come from untrusted sources so this
is considered safe.

OpenSSL 1.1.0 users should upgrade to 1.1.0h
OpenSSL 1.0.2 users should upgrade to 1.0.2o
 
This issue was reported to OpenSSL on 4th January 2018 by the OSS-fuzz project.
The fix was developed by Matt Caswell of the OpenSSL development team.

Written by xorl

March 30, 2018 at 19:41

Posted in vulnerabilities

CVE-2017-12762: Linux kernel isdn_net IOCTL memory corruption

leave a comment »

Recently I came across this report which is kind of sad since this was one nice and funny 0day that had been around for very long time. However, in this post I will only talk about the vulnerability since no exploit has been publicly disclosed yet. The vulnerability is in I4L (ISDN 4 Linux) and starts with the IIOCNETASL (Create slave interface) IOCTL command which is in drivers/isdn/i4l/isdn_common.c as shown below.

static int
isdn_ioctl(struct file *file, uint cmd, ulong arg)
{
   ...
		switch (cmd) {
   ...

		case IIOCNETASL:
			/* Add a slave to a network-interface */
			if (arg) {
				if (copy_from_user(bname, argp, sizeof(bname) - 1))
					return -EFAULT;
			} else
				return -EINVAL;
			ret = mutex_lock_interruptible(&dev->mtx);
			if (ret) return ret;
			if ((s = isdn_net_newslave(bname))) {
				if (copy_to_user(argp, s, strlen(s) + 1)) {
   ...
}

Basically this is accessible via /dev/isdnctrl ISDN control device and ioctl(2) system call using IIOCNETASL command. As you can see in the above snippet, it uses copy_from_user() to get the user controlled buffer and store it in “bname” which is a stack allocated buffer that you can see how it was defined below.

	union iocpar {
		char name[10];
		char bname[22];
		isdn_ioctl_struct iocts;
		isdn_net_ioctl_phone phone;
		isdn_net_ioctl_cfg cfg;
	} iocpar;
	void __user *argp = (void __user *)arg;

#define name  iocpar.name
#define bname iocpar.bname

Later on we see that the user derived “bname” buffer is passed to isdn_net_newslave() which is a function defined in drivers/isdn/i4l/isdn_net.c. Here is this function.

char *
isdn_net_newslave(char *parm)
{
	char *p = strchr(parm, ',');
	isdn_net_dev *n;
	char newname[10];

	if (p) {
		/* Slave-Name MUST not be empty */
		if (!strlen(p + 1))
			return NULL;
		strcpy(newname, p + 1);
		*p = 0;
   ...
}

Here we can see that the only check on the user derived “p” pointer is that it is not empty. Then it uses strcpy() to copy the contents of it to “newname” which is a stack buffer with size of 10 Bytes. This is like a 90s textbook stack buffer overflow. In August 2017 it was reported and patched by Annie Cherkaev by replacing strcpy() with strscpy() which ensures that the copy will not exceed “newname” buffer’s limits. The patch is the following.

--- a/drivers/isdn/i4l/isdn_common.c
+++ b/drivers/isdn/i4l/isdn_common.c
@@ -1379,6 +1379,7 @@  isdn_ioctl(struct file *file, uint cmd,
 			if (arg) {
 				if (copy_from_user(bname, argp, sizeof(bname) - 1))
 					return -EFAULT;
+				bname[sizeof(bname)-1] = 0;
 			} else
 				return -EINVAL;
 			ret = mutex_lock_interruptible(&dev->mtx);
--- a/drivers/isdn/i4l/isdn_net.c
+++ b/drivers/isdn/i4l/isdn_net.c
@@ -2611,10 +2611,9 @@  isdn_net_newslave(char *parm)
 	char newname[10];
 
 	if (p) {
-		/* Slave-Name MUST not be empty */
-		if (!strlen(p + 1))
+		/* Slave-Name MUST not be empty or overflow 'newname' */
+		if (strscpy(newname, p + 1, sizeof(newname)) <= 0)
 			return NULL;
-		strcpy(newname, p + 1);
 		*p = 0;
 		/* Master must already exist */
 		if (!(n = isdn_net_findif(parm)))

This is kind of sad, not because this is a useful 0day but because it had been around for years. Me and some friends had this 0day literally from 2007 so it is kind of sad seeing it dying quietly like this. In any case, I will not go into how to exploit it but it is a nice trivial vulnerability if you want to play around and practice your Linux kernel stack memory corruption exploitation techniques.

Written by xorl

February 24, 2018 at 02:37

Posted in vulnerabilities

CVE-2017-17046: Xen ARM Information Leak

leave a comment »

This vulnerability was released with XSA-245 security advisory by Julien Grall of ARM. The issue is simply the lack of cleaning up memory which means that one domain might end up having access to uncleaned memory of another domain after reboots (soft or hard). This vulnerability affects only ARM because they do not support NUMA and, as J. Grall explained, specific regions (like the Dom0 kernel) are marked as reserved until the hardware domain is built and they are subsequently copied to its memory. To fix this, xen/common/page_alloc.c was modified as you see below.

diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
index 0b9f6cc6df..fbe5a8af39 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1700,6 +1700,16 @@ static void init_heap_pages(
 {
     unsigned long i;
 
+    /*
+     * Some pages may not go through the boot allocator (e.g reserved
+     * memory at boot but released just after --- kernel, initramfs,
+     * etc.).
+     * Update first_valid_mfn to ensure those regions are covered.
+     */
+    spin_lock(&heap_lock);
+    first_valid_mfn = min_t(unsigned long, page_to_mfn(pg), first_valid_mfn);
+    spin_unlock(&heap_lock);
+
     for ( i = 0; i < nr_pages; i++ )
     {
         unsigned int nid = phys_to_nid(page_to_maddr(pg+i));

And in the same source code file, the equivalent helpers were added to make the above change feasible.

diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
index fbe5a8af39..472c6fe329 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -192,7 +192,11 @@ PAGE_LIST_HEAD(page_broken_list);
  * BOOT-TIME ALLOCATOR
  */
 
-static unsigned long __initdata first_valid_mfn = ~0UL;
+/*
+ * first_valid_mfn is exported because it is use in ARM specific NUMA
+ * helpers. See comment in asm-arm/numa.h.
+ */
+unsigned long first_valid_mfn = ~0UL;
 
 static struct bootmem_region {
     unsigned long s, e; /* MFNs @s through @e-1 inclusive are free */
diff --git a/xen/include/asm-arm/numa.h b/xen/include/asm-arm/numa.h
index a2c1a3476d..3e7384da9e 100644
--- a/xen/include/asm-arm/numa.h
+++ b/xen/include/asm-arm/numa.h
@@ -12,9 +12,15 @@ static inline __attribute__((pure)) nodeid_t phys_to_nid(paddr_t addr)
     return 0;
 }
 
+/*
+ * TODO: make first_valid_mfn static when NUMA is supported on Arm, this
+ * is required because the dummy helpers is using it.
+ */
+extern unsigned long first_valid_mfn;
+
 /* XXX: implement NUMA support */
-#define node_spanned_pages(nid) (total_pages)
-#define node_start_pfn(nid) (pdx_to_pfn(frametable_base_pdx))
+#define node_spanned_pages(nid) (max_page - first_valid_mfn)
+#define node_start_pfn(nid) (first_valid_mfn)
 #define __node_distance(a, b) (20)

Written by xorl

January 22, 2018 at 10:15

Posted in vulnerabilities

Linux kernel RDMA NULL pointer dereference

leave a comment »

While reading through Linux kernel’s ChangeLog I came across this one. I was unable to find any CVE ID for this vulnerability reported by Håkon Bugge on 6 December 2017. The vulnerability was discovered by Google’s syzkaller kernel fuzzer and reported in syzkaller719569. The vulnerable code starts in the setsockopt() system call implementation for RDS (Reliable Datagram Sockets). This code is located at net/rds/af_rds.c and here is the code path we are interested in.

static int rds_setsockopt(struct socket *sock, int level, int optname,
			  char __user *optval, unsigned int optlen)
{
	struct rds_sock *rs = rds_sk_to_rs(sock->sk);
	int ret;
   ...
	case RDS_GET_MR:
		ret = rds_get_mr(rs, optval, optlen);
		break;
	case RDS_GET_MR_FOR_DEST:
		ret = rds_get_mr_for_dest(rs, optval, optlen);
		break;
   ...
}

Before we check those two options, it is important to see some specific value in “rds_sock” structure. This structure is defined in net/rds/rds.h header file. The key here is that “rs_transport” pointer in “rds_sock” structure can be NULL.

/**
 * struct rds_transport -  transport specific behavioural hooks
   ...
 */
   ...
struct rds_transport {
   ...
};

struct rds_sock {
   ...
	/*
	 * bound_addr used for both incoming and outgoing, no INADDR_ANY
	 * support.
	 */
   ...
	struct rds_transport    *rs_transport;
   ...
};

Back in rds_setsockopt(), there are two options with very similar code. The RDS_GET_MR and RDS_GET_MR_FOR_DES which will both result in a code that uses copy_from_user() to copy the “rds_get_mr_args” or “rds_get_mr_for_dest_args” structure to the kernel space, and then invoke __rds_rdma_map() routine to do the mapping.

int rds_get_mr(struct rds_sock *rs, char __user *optval, int optlen)
{
	struct rds_get_mr_args args;
   ...
	if (copy_from_user(&args, (struct rds_get_mr_args __user *)optval,
			   sizeof(struct rds_get_mr_args)))
		return -EFAULT;

	return __rds_rdma_map(rs, &args, NULL, NULL);
}

int rds_get_mr_for_dest(struct rds_sock *rs, char __user *optval, int optlen)
{
   ...
	if (copy_from_user(&args, (struct rds_get_mr_for_dest_args __user *)optval,
			   sizeof(struct rds_get_mr_for_dest_args)))
		return -EFAULT;
   ...
	return __rds_rdma_map(rs, &new_args, NULL, NULL);
}

If we now move to __rds_rdma_map() in net/rds/rdma.c we will quickly notice what’s the issue that triggers the vulnerability. The code will try to access the get_mr() callback function from the “rs_transport” pointer which as mentioned earlier can be unset (NULL), resulting in a NULL pointer dereference. This code path can be reached from the previously described setsockopt() system call options.

static int __rds_rdma_map(struct rds_sock *rs, struct rds_get_mr_args *args,
				u64 *cookie_ret, struct rds_mr **mr_ret)
{
   ...
	if (!rs->rs_transport->get_mr) {
		ret = -EOPNOTSUPP;
		goto out;
	}
   ...
	return ret;
}

The patch was to add an additional check that ensures that “rs_transport” is not NULL. If it’s NULL the __rds_rdma_map() will exit with “ENOTCONN” (Transport endpoint is not connected) error code.

diff --git a/net/rds/rdma.c b/net/rds/rdma.c
index 8886f15abe90..bc2f1e0977d6 100644
--- a/net/rds/rdma.c
+++ b/net/rds/rdma.c
@@ -183,7 +183,7 @@  static int __rds_rdma_map(struct rds_sock *rs, struct rds_get_mr_args *args,
 	long i;
 	int ret;
 
-	if (rs->rs_bound_addr == 0) {
+	if (rs->rs_bound_addr == 0 || !rs->rs_transport) {
 		ret = -ENOTCONN; /* XXX not a great errno */
 		goto out;
 	}

Written by xorl

December 18, 2017 at 23:19

Posted in vulnerabilities