Bug 206579 - amr(4): Multiple vulnerabilities in AMR ioctl handler
Summary: amr(4): Multiple vulnerabilities in AMR ioctl handler
Status: Closed Not A Bug
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: CURRENT
Hardware: Any Any
: Normal Affects Some People
Assignee: freebsd-bugs mailing list
URL:
Keywords: needs-patch, needs-qa, security
Depends on:
Blocks:
 
Reported: 2016-01-24 15:51 UTC by CTurt
Modified: 2016-01-25 22:29 UTC (History)
3 users (show)

See Also:
koobs: mfc-stable10?
koobs: mfc-stable9?


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description CTurt 2016-01-24 15:51:03 UTC
The `amr_ioctl` handler contains userland dereferences, and no bound checks on user supplied sizes.

The only time where the `addr` is correctly accessed by `copyin` is in the Linux emulation commands, like `0xc06e6d00`:

    error = copyin(addr, &ali, sizeof(ali));

The rest of the commands use a union called `arg` is setup to make incorrectly dealing with `addr` easier:

    union {
        void			*_p;
        struct amr_user_ioctl	*au;
    #ifdef AMR_IO_COMMAND32
        struct amr_user_ioctl32	*au32;
    #endif
        int			*result;
    } arg;
    
    ...
    
    arg._p = (void *)addr;

The most serious issue is the `AMR_IO_VERSION` command, writing its output directly without using `copyout`:

case AMR_IO_VERSION:
	debug(1, "AMR_IO_VERSION");
	*arg.result = AMR_IO_VERSION_NUMBER;
	return(0);

The address of this write is completely user controlled, and can be used to write arbitrary kernel memory.

Another issue stems from supplying the `AMR_IO_COMMAND` command. A user supplied size will be fetched (without `copyin`):

    au_length = arg.au->au_length;

Which is then used by `malloc` and `copyin` without any boundary checks:

/* handle inbound data buffer */
real_length = amr_ioctl_buffer_length(au_length);
dp = malloc(real_length, M_AMR, M_WAITOK|M_ZERO);
if (au_length != 0 && au_cmd[0] != 0x06) {
	if ((error = copyin(au_buffer, dp, au_length)) != 0) {
		free(dp, M_AMR);
		return (error);
	}
	debug(2, "copyin %ld bytes from %p -> %p", au_length, au_buffer, dp);
}

On FreeBSD 9, we could abuse the 32bit size truncation in `uma_large_malloc` to get a heap overflow from this. On later versions, allocating large sizes can probably only be used to DoS the system.
Comment 1 CTurt 2016-01-24 16:07:04 UTC
Forgot to mention, the file is `sys/dev/amr/amr.c`.
Comment 2 CTurt 2016-01-25 07:23:54 UTC
This code could be explained if `addr` can be either a user or kernel pointer depending on `cmd`, but I'd like this to be confirmed.
Comment 3 CTurt 2016-01-25 22:29:48 UTC
There are similarities in other drivers, like the `mfi` code.

For Linux commands, like `MFI_LINUX_CMD_2`, `copyin` is used:

    error = copyin(arg, &l_ioc, sizeof(l_ioc));

But for FreeBSD commands, such as `MFIIO_QUERY_DISK`, `arg` is directly dereferenced:

    qd = (struct mfi_query_disk *)arg;
    qd->present = 1;

Since both drivers seem to follow this same pattern, I believe the handling of `addr` is probably correct.