Skip to content

Commit 8a703fa

Browse files
danobikernel-patches-bot
authored andcommitted
lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
do_strncpy_from_user() may copy some extra bytes after the NUL terminator into the destination buffer. This usually does not matter for normal string operations. However, when BPF programs key BPF maps with strings, this matters a lot. A BPF program may read strings from user memory by calling the bpf_probe_read_user_str() helper which eventually calls do_strncpy_from_user(). The program can then key a map with the resulting string. BPF map keys are fixed-width and string-agnostic, meaning that map keys are treated as a set of bytes. The issue is when do_strncpy_from_user() overcopies bytes after the NUL terminator, it can result in seemingly identical strings occupying multiple slots in a BPF map. This behavior is subtle and totally unexpected by the user. This commit uses the proper word-at-a-time APIs to avoid overcopying. Fixes: 6ae08ae ("bpf: Add probe_read_{user, kernel} and probe_read_{user, kernel}_str helpers") Signed-off-by: Daniel Xu <[email protected]>
1 parent 0a8ca23 commit 8a703fa

File tree

2 files changed

+27
-2
lines changed

2 files changed

+27
-2
lines changed

kernel/trace/bpf_trace.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ bpf_probe_read_user_str_common(void *dst, u32 size,
181181
{
182182
int ret;
183183

184+
/*
185+
* NB: We rely on strncpy_from_user() not copying junk past the NUL
186+
* terminator into `dst`.
187+
*
188+
* strncpy_from_user() does long-sized strides in the fast path. If the
189+
* strncpy does not mask out the bytes after the NUL in `unsafe_ptr`,
190+
* then there could be junk after the NUL in `dst`. If user takes `dst`
191+
* and keys a hash map with it, then semantically identical strings can
192+
* occupy multiple entries in the map.
193+
*/
184194
ret = strncpy_from_user_nofault(dst, unsafe_ptr, size);
185195
if (unlikely(ret < 0))
186196
memset(dst, 0, size);

lib/strncpy_from_user.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,32 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src,
3535
goto byte_at_a_time;
3636

3737
while (max >= sizeof(unsigned long)) {
38-
unsigned long c, data;
38+
unsigned long c, data, mask;
3939

4040
/* Fall back to byte-at-a-time if we get a page fault */
4141
unsafe_get_user(c, (unsigned long __user *)(src+res), byte_at_a_time);
4242

43-
*(unsigned long *)(dst+res) = c;
43+
/*
44+
* Note that we mask out the bytes following the NUL. This is
45+
* important to do because string oblivious code may read past
46+
* the NUL. For those routines, we don't want to give them
47+
* potentially random bytes after the NUL in `src`.
48+
*
49+
* One example of such code is BPF map keys. BPF treats map keys
50+
* as an opaque set of bytes. Without the post-NUL mask, any BPF
51+
* maps keyed by strings returned from strncpy_from_user() may
52+
* have multiple entries for semantically identical strings.
53+
*/
4454
if (has_zero(c, &data, &constants)) {
4555
data = prep_zero_mask(c, data, &constants);
4656
data = create_zero_mask(data);
57+
mask = zero_bytemask(data);
58+
*(unsigned long *)(dst+res) = c & mask;
4759
return res + find_zero(data);
4860
}
61+
62+
*(unsigned long *)(dst+res) = c;
63+
4964
res += sizeof(unsigned long);
5065
max -= sizeof(unsigned long);
5166
}

0 commit comments

Comments
 (0)