Summary: | Multiple bugs in mpr ioctl handler | ||
---|---|---|---|
Product: | Base System | Reporter: | CTurt <ecturt> |
Component: | kern | Assignee: | freebsd-bugs (Nobody) <bugs> |
Status: | New --- | ||
Severity: | Affects Only Me | CC: | cem, emaste, op, slm |
Priority: | --- | ||
Version: | CURRENT | ||
Hardware: | Any | ||
OS: | Any |
Description
CTurt
2017-05-25 12:02:42 UTC
Thanks for the report. I know there are a few issues there, especially with the driver not checking for bad user supplied values. I agree with most of your comments, but I have a question. In this section: ..... mpr_lock(sc); size = data->Size; if ((size >= sizeof(sc->recorded_events)) && (status == 0)) { mpr_unlock(sc); if (copyout((void *)sc->recorded_events, PTRIN(data->PtrEvents), size) != 0) status = EFAULT; mpr_lock(sc); } else { /* * data->Size value is not large enough to copy event data. */ status = EFAULT; } /* * Change size value to match the number of bytes that were copied. */ if (status == 0) data->Size = sizeof(sc->recorded_events); mpr_unlock(sc); ..... You say: "It checks if the user supplied size is greater than the the size of the struct, and if so, copies using the size which it just determined was too large. It should `copyout` using `sizeof(sc->recorded_events)`." But this is just checking if the user supplied size is enough to hold sizeof(sc->recored_events). It's not checking if it was too large, it's checking if it's large enough. If that user supplied size is >= to the data being copied, it should be OK. Do you agree? I'm not sure I like that data->Size is being changed from the user supplied value, but that's something different. Maybe that's OK, but not sure. It could be that this is a normal technique used to return the number of bytes that are valid in the user buffer, but I only see that being done in this one function. Thanks for taking a look. To clarify the point you asked about: `recorded_events` is a fixed size, it looks like this in `struct mpr_softc`: mpr_event_entry_t recorded_events[MPR_EVENT_QUEUE_SIZE]; The kernel should copy out only `sizeof(sc->recored_events)` bytes at most from here, or else it will copy out of bounds memory to userland. Going back to the code: size = data->Size; if ((size >= sizeof(sc->recorded_events)) && (status == 0)) { mpr_unlock(sc); if (copyout((void *)sc->recorded_events, PTRIN(data->PtrEvents), size) != 0) status = EFAULT; mpr_lock(sc); } Notice that it passes `size` to `copyout`, which can be greater than `sizeof(sc->recorded_events)` (see the check). Instead, the `copyout` call should be passed `sizeof(sc->recorded_events)` so that it won't read more than the array contains. So, you are correct when you say that it "is just checking if the user supplied size is enough to hold sizeof(sc->recored_events)". But this check won't prevent copying out of bounds memory to userland. OK. I see what you're saying. The check makes sure that data is not copied to invalid space, but it does not check if the bytes are valid. That's true. Maybe it's better like this: if (status == 0) { if (copyout((void *)sc->recorded_events, PTRIN(data->PtrEvents), min(size, sizeof(sc->recorded_events))) != 0) status = EFAULT; } Then, it just copies out as many valid bytes as it can, and no 'else' part. And there's no reason to look at 'status' in the 'if' actually, so just: if (copyout((void *)sc->recorded_events, PTRIN(data->PtrEvents), min(size, sizeof(sc->recorded_events))) != 0) status = EFAULT; That's correct, using the `min` macro will correct that issue. |