Skip to contentSkip to conversation

mm: userfaultfd: add new UFFDIO_POISON ioctl: fix

PATCH Applied Hugh Dickins 3 participants 4 messages Jul 12, 2023 – Jul 19, 2023

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 mm: userfaultfd: add new UFFDIO_POISON ioctl: fix

Smatch has observed that pte_offset_map_lock() is now allowed to fail, and then ptl should not be unlocked. Use -EAGAIN here like elsewhere.

---
Axel, Peter: this seems right as a fix to the patch in mm-unstable; but in preparing this, I noticed mfill_atomic()'s code before calling mfill_atomic_pte(), and think that my original choice of -EFAULT was therefore better than -EAGAIN for all of these; and that mfill_atomic()'s BUG_ONs there would be better deleted (and is its BUG_ON(folio) safe??). Something one of us should address, after this fixup is in akpm's tree.

 mm/userfaultfd.c | 4 ++++
 1 file changed, 4 insertions(+)
mm/userfaultfd.c +4 -0
@@ -300,7 +300,10 @@ static int mfill_atomic_pte_poison(pmd_t *dst_pmd,
 	spinlock_t *ptl;
 
 	_dst_pte = make_pte_marker(PTE_MARKER_POISONED);
+	ret = -EAGAIN;
 	dst_pte = pte_offset_map_lock(dst_mm, dst_pmd, dst_addr, &ptl);
+	if (!dst_pte)
+		goto out;
 
 	if (mfill_file_over_size(dst_vma, dst_addr)) {
 		ret = -EFAULT;
@@ -319,6 +322,7 @@ static int mfill_atomic_pte_poison(pmd_t *dst_pmd,
 	ret = 0;
 out_unlock:
 	pte_unmap_unlock(dst_pte, ptl);
+out:
 	return ret;
 }
 
Reviewed-by on v1 1/1
#1 lore raw
Quoted 5 lines · Hugh Dickins

Smatch has observed that pte_offset_map_lock() is now allowed to fail, and then ptl should not be unlocked. Use -EAGAIN here like elsewhere.

Looks correct to me, thanks Hugh! If it's useful, feel free to take:

Quoted 7 lines

---
Axel, Peter: this seems right as a fix to the patch in mm-unstable; but in preparing this, I noticed mfill_atomic()'s code before calling mfill_atomic_pte(), and think that my original choice of -EFAULT was therefore better than -EAGAIN for all of these; and that mfill_atomic()'s BUG_ONs there would be better deleted (and is its BUG_ON(folio) safe??). Something one of us should address, after this fixup is in akpm's tree.

Agreed, dealing with the BUG_ONs is something I have been meaning to do as checkpatch bothers me about it frequently.

What this code is trying to do is to enforce the contract that: if mfill_atomic_pte failed, then it must have released *folio and set it to NULL. But, you're exactly right that if we had such a bug, it would mean we leaked one page in an unusual error case - not great, but not worth crashing the kernel either.

For UFFDIO_POISON this doesn't really apply because it doesn't allocate or get a reference to a page in any case.

For other cases like UFFDIO_COPY where it does matter, I think it's fine as is regardless of the error code returned (as long as it's not -ENOENT :) ). mfill_atomic_pte_copy() is sure to set *foliop = NULL before it attempts this lock (in mfill_atomic_install_pte). So -EAGAIN or -EFAULT should work equally well at least from that perspective. I somewhat prefer -EAGAIN, but maybe I'm missing something.

Quoted 25 lines
 mm/userfaultfd.c | 4 ++++
 1 file changed, 4 insertions(+)
mm/userfaultfd.c +4 -0
@@ -300,7 +300,10 @@ static int mfill_atomic_pte_poison(pmd_t *dst_pmd,
        spinlock_t *ptl;
 
        _dst_pte = make_pte_marker(PTE_MARKER_POISONED);
+       ret = -EAGAIN;
        dst_pte = pte_offset_map_lock(dst_mm, dst_pmd, dst_addr, &ptl);
+       if (!dst_pte)
+               goto out;
 
        if (mfill_file_over_size(dst_vma, dst_addr)) {
                ret = -EFAULT;
@@ -319,6 +322,7 @@ static int mfill_atomic_pte_poison(pmd_t *dst_pmd,
        ret = 0;
 out_unlock:
        pte_unmap_unlock(dst_pte, ptl);
+out:
        return ret;
 }
 
#2 lore raw
Quoted 10 lines · Axel Rasmussen
Quoted 5 lines · Hugh Dickins

Smatch has observed that pte_offset_map_lock() is now allowed to fail, and then ptl should not be unlocked. Use -EAGAIN here like elsewhere.

Looks correct to me, thanks Hugh! If it's useful, feel free to take:

Thanks, that will have helped Andrew (but would vanish into the merge with your original when it moves from mm-unstable to mm-stable).

Quoted 21 lines
Quoted 8 lines

---
Axel, Peter: this seems right as a fix to the patch in mm-unstable; but in preparing this, I noticed mfill_atomic()'s code before calling mfill_atomic_pte(), and think that my original choice of -EFAULT was therefore better than -EAGAIN for all of these; and that mfill_atomic()'s BUG_ONs there would be better deleted (and is its BUG_ON(folio) safe??). Something one of us should address, after this fixup is in akpm's tree.

Agreed, dealing with the BUG_ONs is something I have been meaning to do as checkpatch bothers me about it frequently.

What this code is trying to do is to enforce the contract that: if mfill_atomic_pte failed, then it must have released *folio and set it to NULL. But, you're exactly right that if we had such a bug, it would mean we leaked one page in an unusual error case - not great, but not worth crashing the kernel either.

For UFFDIO_POISON this doesn't really apply because it doesn't allocate or get a reference to a page in any case.

Quoted 6 lines

For other cases like UFFDIO_COPY where it does matter, I think it's fine as is regardless of the error code returned (as long as it's not -ENOENT :) ). mfill_atomic_pte_copy() is sure to set *foliop = NULL before it attempts this lock (in mfill_atomic_install_pte). So -EAGAIN or -EFAULT should work equally well at least from that perspective. I somewhat prefer -EAGAIN, but maybe I'm missing something.

Right, when I said -EFAULT better than -EAGAIN, I was only basing that on -EFAULT being the code which was already used for pmd_trans_huge() in the check above mfill_atomic()'s call to mfill_atomic_pte().

I don't disagree with you, and Peter too preferred -EAGAIN: it's just better if they all use the same code (or maybe mfill_atomic()'s first check for pmd_trans_huge() can be deleted along with the BUG_ONs - but I have not checked through the cases, there may be very good reason to keep that preliminary check there).

Hugh

reviewer
#3 lore raw

Hello, Hugh, Axel,

Sorry for a very late reply after a long PTO..

Quoted 6 lines · Hugh Dickins
Quoted 5 lines · Axel Rasmussen
Quoted 4 lines · Hugh Dickins

Smatch has observed that pte_offset_map_lock() is now allowed to fail, and then ptl should not be unlocked. Use -EAGAIN here like elsewhere.

Quoted 26 lines
Quoted 5 lines
Quoted 1 line

Looks correct to me, thanks Hugh! If it's useful, feel free to take:

Thanks, that will have helped Andrew (but would vanish into the merge with your original when it moves from mm-unstable to mm-stable).

Quoted 17 lines
Quoted 8 lines

---
Axel, Peter: this seems right as a fix to the patch in mm-unstable; but in preparing this, I noticed mfill_atomic()'s code before calling mfill_atomic_pte(), and think that my original choice of -EFAULT was therefore better than -EAGAIN for all of these; and that mfill_atomic()'s BUG_ONs there would be better deleted (and is its BUG_ON(folio) safe??). Something one of us should address, after this fixup is in akpm's tree.

Agreed, dealing with the BUG_ONs is something I have been meaning to do as checkpatch bothers me about it frequently.

What this code is trying to do is to enforce the contract that: if mfill_atomic_pte failed, then it must have released *folio and set it to NULL. But, you're exactly right that if we had such a bug, it would mean we leaked one page in an unusual error case - not great, but not worth crashing the kernel either.

Yes I also agree the BUG_ON might be a bit too aggressive, even if still accurate (so we should really not trigger that). WARN_ON_ONCE() should be enough.

Quoted 15 lines
Quoted 10 lines

For UFFDIO_POISON this doesn't really apply because it doesn't allocate or get a reference to a page in any case.

For other cases like UFFDIO_COPY where it does matter, I think it's fine as is regardless of the error code returned (as long as it's not -ENOENT :) ). mfill_atomic_pte_copy() is sure to set *foliop = NULL before it attempts this lock (in mfill_atomic_install_pte). So -EAGAIN or -EFAULT should work equally well at least from that perspective. I somewhat prefer -EAGAIN, but maybe I'm missing something.

Right, when I said -EFAULT better than -EAGAIN, I was only basing that on -EFAULT being the code which was already used for pmd_trans_huge() in the check above mfill_atomic()'s call to mfill_atomic_pte().

Quoted 5 lines

I don't disagree with you, and Peter too preferred -EAGAIN: it's just better if they all use the same code (or maybe mfill_atomic()'s first check for pmd_trans_huge() can be deleted along with the BUG_ONs - but I have not checked through the cases, there may be very good reason to keep that preliminary check there).

It's just that -EFAULT will definitely fail most of the uffd based apps, because no app can handle an -EFAULT over any ioctl(UFFDIO_*). So if we'd delete the 1st pmd_trans_huge() check we may want to make the 2nd return -EAGAIN.

I think this -EFAULT (of 2nd pmd_trans_huge() check) should really also be an -EAGAIN, I'd bet ten dollars if it's really hit somewhere before it'll crash the app as explained.

With Hugh's recent rework on pte_offset_map_lock() (which should contain a thp check within the map_lock()) IMHO we can safely drop the 2nd check as a whole (while keeping the 1st check as a fast path to detect existing thps), and then we should reliably return an -EAGAIN if a thp raced with us, so the userapp should just retry when race with anything else.

Thanks,

Signature · 1 line
Peter Xu