diff options
author | Daniel Thompson <daniel.thompson@linaro.org> | 2022-04-26 12:22:37 +0100 |
---|---|---|
committer | Daniel Thompson <daniel.thompson@linaro.org> | 2022-04-26 12:22:37 +0100 |
commit | 22c2305444157f520cec2c5e8ced9f133ac3b99d (patch) | |
tree | 270d6b1cb3041973b29c5d30a2692091b42fe5c1 | |
parent | f443e374ae131c168a065ea1748feac6b2e76613 (diff) |
arm64: mte: Handle zero-length reads in access_remote_tags()
Currently, if the user sends uiov.iov_len == 0 then
__access_remote_tags() will choose its return value based on an
undefined value found on the stack. I think it can only ever leak the
value of the sign-bit but a leak is a leak so let's fix this by
explicitly handling zero length requests.
The choice of return code is not entirely clear-cut. However I think the
most reasonable return value is to confirm that we have successfully
performed no work.
Discovered whilst experimenting with clang-analyzer (13). The problem was
reported by the tooling as follows:
arch/arm64/kernel/mte.c:398:11: warning: The left operand of '<=' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult]
if (ret <= 0)
^
.../arch/arm64/kernel/mte.c:418:6: note: Assuming 'mm' is non-null
if (!mm)
^~~
.../arch/arm64/kernel/mte.c:418:2: note: Taking false branch
if (!mm)
^
.../arch/arm64/kernel/mte.c:421:6: note: Assuming field 'ptrace' is not equal to 0
if (!tsk->ptrace || (current != tsk->parent) ||
^~~~~~~~~~~~
.../arch/arm64/kernel/mte.c:421:6: note: Left side of '||' is false
.../arch/arm64/kernel/mte.c:421:23: note: Assuming the condition is false
if (!tsk->ptrace || (current != tsk->parent) ||
^
./arch/arm64/include/asm/current.h:24:17: note: expanded from macro 'current'
^
.../arch/arm64/kernel/mte.c:421:6: note: Left side of '||' is false
if (!tsk->ptrace || (current != tsk->parent) ||
^
.../arch/arm64/kernel/mte.c:422:8: note: Assuming the condition is false
((get_dumpable(mm) != SUID_DUMP_USER) &&
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../arch/arm64/kernel/mte.c:422:44: note: Left side of '&&' is false
((get_dumpable(mm) != SUID_DUMP_USER) &&
^
.../arch/arm64/kernel/mte.c:428:8: note: Calling '__access_remote_tags'
ret = __access_remote_tags(mm, addr, kiov, gup_flags);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../arch/arm64/kernel/mte.c:338:2: note: 'ret' declared without an initial value
int ret;
^~~~~~~
.../arch/arm64/kernel/mte.c:341:6: note: Assuming the condition is false
if (!access_ok(buf, len))
^~~~~~~~~~~~~~~~~~~~
.../arch/arm64/kernel/mte.c:341:2: note: Taking false branch
if (!access_ok(buf, len))
^
.../arch/arm64/kernel/mte.c:344:6: note: Calling 'mmap_read_lock_killable'
if (mmap_read_lock_killable(mm))
^~~~~~~~~~~~~~~~~~~~~~~~~~~
..././include/linux/mmap_lock.h:127:48: note: Assuming 'ret' is equal to 0, which participates in a condition later
__mmap_lock_trace_acquire_returned(mm, false, ret == 0);
^~~~~~~~
..././include/linux/mmap_lock.h:128:2: note: Returning zero (loaded from 'ret'), which participates in a condition later
return ret;
^~~~~~~~~~
.../arch/arm64/kernel/mte.c:344:6: note: Returning from 'mmap_read_lock_killable'
if (mmap_read_lock_killable(mm))
^~~~~~~~~~~~~~~~~~~~~~~~~~~
.../arch/arm64/kernel/mte.c:344:2: note: Taking false branch
if (mmap_read_lock_killable(mm))
^
.../arch/arm64/kernel/mte.c:347:2: note: Loop condition is false. Execution continues on line 392
while (len) {
^
.../arch/arm64/kernel/mte.c:396:13: note: Field 'iov_len' is 0
if (!kiov->iov_len) {
^
.../arch/arm64/kernel/mte.c:396:2: note: Taking true branch
if (!kiov->iov_len) {
^
.../arch/arm64/kernel/mte.c:398:11: note: The left operand of '<=' is a garbage value
if (ret <= 0)
~~~ ^
Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
-rw-r--r-- | arch/arm64/kernel/mte.c | 3 |
1 files changed, 3 insertions, 0 deletions
diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c index f418ebc65f95..a6905284b0f4 100644 --- a/arch/arm64/kernel/mte.c +++ b/arch/arm64/kernel/mte.c @@ -338,6 +338,9 @@ static int __access_remote_tags(struct mm_struct *mm, unsigned long addr, int ret; int write = gup_flags & FOLL_WRITE; + if (len == 0) + return 0; + if (!access_ok(buf, len)) return -EFAULT; |