The results of "access_ok()" can be mis-speculated. The result is that
you can end speculatively:
if (access_ok(from, size))
// Right here
For the same reason as done in copy_from_user() by
commit 74e19ef0ff80 ("uaccess: Add speculation barrier to
copy_from_user()"), add a speculation barrier to copy_from_user_iter().
See commit 74e19ef0ff80 ("uaccess: Add speculation barrier to
copy_from_user()") for more details.
Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
---
lib/iov_iter.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index f9193f952f49..ebf524a37907 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -50,6 +50,13 @@ size_t copy_from_user_iter(void __user *iter_from, size_t progress,
if (should_fail_usercopy())
return len;
if (access_ok(iter_from, len)) {
+ /*
+ * Ensure that bad access_ok() speculation will not
+ * lead to nasty side effects *after* the copy is
+ * finished:
+ */
+ barrier_nospec();
+
to += progress;
instrument_copy_from_user_before(to, iter_from, len);
res = raw_copy_from_user(to, iter_from, len);
--
2.49.0
On Sun, 22 Jun 2025 11:52:40 +0200
Christophe Leroy <christophe.leroy@csgroup.eu> wrote:
> The results of "access_ok()" can be mis-speculated. The result is that
> you can end speculatively:
>
> if (access_ok(from, size))
> // Right here
>
> For the same reason as done in copy_from_user() by
> commit 74e19ef0ff80 ("uaccess: Add speculation barrier to
> copy_from_user()"), add a speculation barrier to copy_from_user_iter().
I'm sure I sent a patch to change this code to used the 'masked' functions.
Probably ought to be done at the same time.
Would have been early feb, about the time I suggested:
+#ifdef masked_user_access_begin
+#define masked_user_read_access_begin(from, size) \
+ ((*(from) = masked_user_access_begin(*(from))), 1)
+#define masked_user_write_access_begin(from, size) \
+ ((*(from) = masked_user_access_begin(*(from))), 1)
+#else
+#define masked_user_read_access_begin(from, size) \
+ user_read_access_begin(*(from), size)
+#define masked_user_write_access_begin(from, size) \
+ user_write_access_begin(*(from), size)
+#endif
allowing:
- if (!user_read_access_begin(from, sizeof(*from)))
+ if (!masked_user_read_access_begin(&from, sizeof(*from)))
David
>
> See commit 74e19ef0ff80 ("uaccess: Add speculation barrier to
> copy_from_user()") for more details.
>
> Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
> ---
> lib/iov_iter.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/lib/iov_iter.c b/lib/iov_iter.c
> index f9193f952f49..ebf524a37907 100644
> --- a/lib/iov_iter.c
> +++ b/lib/iov_iter.c
> @@ -50,6 +50,13 @@ size_t copy_from_user_iter(void __user *iter_from, size_t progress,
> if (should_fail_usercopy())
> return len;
> if (access_ok(iter_from, len)) {
> + /*
> + * Ensure that bad access_ok() speculation will not
> + * lead to nasty side effects *after* the copy is
> + * finished:
> + */
> + barrier_nospec();
> +
> to += progress;
> instrument_copy_from_user_before(to, iter_from, len);
> res = raw_copy_from_user(to, iter_from, len);
On Sun, 22 Jun 2025 at 02:52, Christophe Leroy
<christophe.leroy@csgroup.eu> wrote:
>
> The results of "access_ok()" can be mis-speculated.
Hmm. This code is critical. I think it should be converted to use that
masked address thing if we have to add it here.
And at some point this access_ok() didn't even exist, because we check
the addresses at iter creation time. So this one might be a "belt and
suspenders" check, rather than something critical.
(Although I also suspect that when we added ITER_UBUF we might have
created cases where those user addresses aren't checked at iter
creation time any more).
Linus
Le 22/06/2025 à 18:57, Linus Torvalds a écrit :
> On Sun, 22 Jun 2025 at 02:52, Christophe Leroy
> <christophe.leroy@csgroup.eu> wrote:
>>
>> The results of "access_ok()" can be mis-speculated.
>
> Hmm. This code is critical. I think it should be converted to use that
> masked address thing if we have to add it here.
Ok, I'll add it.
>
> And at some point this access_ok() didn't even exist, because we check
> the addresses at iter creation time. So this one might be a "belt and
> suspenders" check, rather than something critical.
>
> (Although I also suspect that when we added ITER_UBUF we might have
> created cases where those user addresses aren't checked at iter
> creation time any more).
>
Let's take the follow path as an exemple:
snd_pcm_ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES)
snd_pcm_common_ioctl()
snd_pcm_xferi_frames_ioctl()
snd_pcm_lib_write()
__snd_pcm_lib_xfer()
default_write_copy()
copy_from_iter()
_copy_from_iter()
__copy_from_iter()
iterate_and_advance()
iterate_and_advance2()
iterate_iovec()
copy_from_user_iter()
As far as I can see, none of those functions check the accessibility of
the iovec. Am I missing something ?
Christophe
On Mon, 23 Jun 2025 at 22:49, Christophe Leroy
<christophe.leroy@csgroup.eu> wrote:
>
> >
> > (Although I also suspect that when we added ITER_UBUF we might have
> > created cases where those user addresses aren't checked at iter
> > creation time any more).
> >
>
> Let's take the follow path as an exemple:
>
> snd_pcm_ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES)
> snd_pcm_common_ioctl()
> snd_pcm_xferi_frames_ioctl()
> snd_pcm_lib_write()
> __snd_pcm_lib_xfer()
> default_write_copy()
> copy_from_iter()
> _copy_from_iter()
> __copy_from_iter()
> iterate_and_advance()
> iterate_and_advance2()
> iterate_iovec()
> copy_from_user_iter()
>
> As far as I can see, none of those functions check the accessibility of
> the iovec. Am I missing something ?
So we still to do this checking at creation time (see import_iovec ->
__import_iovec, and import_ubuf).
In the path you give as an example, the check happens at that
"do_transfer()" stage when it does
err = import_ubuf(type, (__force void __user *)data, bytes, &iter);
but yeah, it's very non-obvious (see __snd_pcm_lib_xfer(), which calls
writer() which is either interleaved_copy() or noninterleaved_copy(),
and then they do that do_transfer() thing which does that
import_ubuf() thing.
So *because* you were supposed to have checked your iov_iters
beforehand, the actual iter code itself at some point just used
__copy_to_user() directly with no checking at all.
And that all was really *much* too subtle, and Al fixed this a few
years ago (see commit 09fc68dc66f7: "iov_iter: saner checks on
copyin/copyout")
Linus
On Tue, 24 Jun 2025 07:49:03 +0200 Christophe Leroy <christophe.leroy@csgroup.eu> wrote: > Le 22/06/2025 à 18:57, Linus Torvalds a écrit : > > On Sun, 22 Jun 2025 at 02:52, Christophe Leroy > > <christophe.leroy@csgroup.eu> wrote: > >> > >> The results of "access_ok()" can be mis-speculated. > > > > Hmm. This code is critical. I think it should be converted to use that > > masked address thing if we have to add it here. > > Ok, I'll add it. > > > > > And at some point this access_ok() didn't even exist, because we check > > the addresses at iter creation time. So this one might be a "belt and > > suspenders" check, rather than something critical. > > > > (Although I also suspect that when we added ITER_UBUF we might have > > created cases where those user addresses aren't checked at iter > > creation time any more). > > > > Let's take the follow path as an exemple: > > snd_pcm_ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES) > snd_pcm_common_ioctl() > snd_pcm_xferi_frames_ioctl() > snd_pcm_lib_write() > __snd_pcm_lib_xfer() > default_write_copy() > copy_from_iter() > _copy_from_iter() > __copy_from_iter() > iterate_and_advance() > iterate_and_advance2() > iterate_iovec() > copy_from_user_iter() > > As far as I can see, none of those functions check the accessibility of > the iovec. Am I missing something ? The import_ubuf() in do_transfer() ought to contain one. But really you want the one in copy_from_user_iter() rather than the outer one. Mind you that code is horrid. The code only ever copies a single buffer, so could be much shorter. And is that deep call chain really needed for the very common case of one buffer. David > > Christophe
On Sun, 22 Jun 2025 09:57:20 -0700 Linus Torvalds <torvalds@linux-foundation.org> wrote: > On Sun, 22 Jun 2025 at 02:52, Christophe Leroy > <christophe.leroy@csgroup.eu> wrote: > > > > The results of "access_ok()" can be mis-speculated. > > Hmm. This code is critical. I think it should be converted to use that > masked address thing if we have to add it here. If access_ok() is mis-speculated then you get a read from the user-specified kernel address - I don't think that matters. The hacker would need to find somewhere where the read value was used in a test or memory access so that side effects (typically cache line evictions) can be detected. But copy_from_user_iter() is pretty much always used for 'data' not 'control pane' - so you'd be hard pushed to find somewhere 'useful'. Not only that the cpu would have to return from copy_from_user_iter() before correcting the mis-speculation. I can't imagine that happening - even without all the 'return thunk' stuff. The same might be true for copy_from_user(). It might only be get_user() that actually has any chance of being exploited. > > And at some point this access_ok() didn't even exist, because we check > the addresses at iter creation time. So this one might be a "belt and > suspenders" check, rather than something critical. IIRC there was a patch to move the access_ok() much nearer the use copy. But it didn't go as far as removing the one from import_iovec(). Although removing that one might make sense. (I've also looked about whether the 'direction' is needed in the 'iter'. 98% of the code knows what it should be - and may contain pointless checks, but some bits seem to rely on it.) David > > (Although I also suspect that when we added ITER_UBUF we might have > created cases where those user addresses aren't checked at iter > creation time any more). > > Linus
© 2016 - 2026 Red Hat, Inc.