Skip to content

Commit ee501f8

Browse files
Al Virogregkh
Al Viro
authored andcommitted
fix bitmap corruption on close_range() with CLOSE_RANGE_UNSHARE
commit 9a2fa14 upstream. copy_fd_bitmaps(new, old, count) is expected to copy the first count/BITS_PER_LONG bits from old->full_fds_bits[] and fill the rest with zeroes. What it does is copying enough words (BITS_TO_LONGS(count/BITS_PER_LONG)), then memsets the rest. That works fine, *if* all bits past the cutoff point are clear. Otherwise we are risking garbage from the last word we'd copied. For most of the callers that is true - expand_fdtable() has count equal to old->max_fds, so there's no open descriptors past count, let alone fully occupied words in ->open_fds[], which is what bits in ->full_fds_bits[] correspond to. The other caller (dup_fd()) passes sane_fdtable_size(old_fdt, max_fds), which is the smallest multiple of BITS_PER_LONG that covers all opened descriptors below max_fds. In the common case (copying on fork()) max_fds is ~0U, so all opened descriptors will be below it and we are fine, by the same reasons why the call in expand_fdtable() is safe. Unfortunately, there is a case where max_fds is less than that and where we might, indeed, end up with junk in ->full_fds_bits[] - close_range(from, to, CLOSE_RANGE_UNSHARE) with * descriptor table being currently shared * 'to' being above the current capacity of descriptor table * 'from' being just under some chunk of opened descriptors. In that case we end up with observably wrong behaviour - e.g. spawn a child with CLONE_FILES, get all descriptors in range 0..127 open, then close_range(64, ~0U, CLOSE_RANGE_UNSHARE) and watch dup(0) ending up with descriptor torvalds#128, despite torvalds#64 being observably not open. The minimally invasive fix would be to deal with that in dup_fd(). If this proves to add measurable overhead, we can go that way, but let's try to fix copy_fd_bitmaps() first. * new helper: bitmap_copy_and_expand(to, from, bits_to_copy, size). * make copy_fd_bitmaps() take the bitmap size in words, rather than bits; it's 'count' argument is always a multiple of BITS_PER_LONG, so we are not losing any information, and that way we can use the same helper for all three bitmaps - compiler will see that count is a multiple of BITS_PER_LONG for the large ones, so it'll generate plain memcpy()+memset(). Reproducer added to tools/testing/selftests/core/close_range_test.c Cc: [email protected] Signed-off-by: Al Viro <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 1887299 commit ee501f8

File tree

2 files changed

+25
-17
lines changed

2 files changed

+25
-17
lines changed

fs/file.c

+13-17
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,23 @@ static void free_fdtable_rcu(struct rcu_head *rcu)
4141
#define BITBIT_NR(nr) BITS_TO_LONGS(BITS_TO_LONGS(nr))
4242
#define BITBIT_SIZE(nr) (BITBIT_NR(nr) * sizeof(long))
4343

44+
#define fdt_words(fdt) ((fdt)->max_fds / BITS_PER_LONG) // words in ->open_fds
4445
/*
4546
* Copy 'count' fd bits from the old table to the new table and clear the extra
4647
* space if any. This does not copy the file pointers. Called with the files
4748
* spinlock held for write.
4849
*/
49-
static void copy_fd_bitmaps(struct fdtable *nfdt, struct fdtable *ofdt,
50-
unsigned int count)
50+
static inline void copy_fd_bitmaps(struct fdtable *nfdt, struct fdtable *ofdt,
51+
unsigned int copy_words)
5152
{
52-
unsigned int cpy, set;
53-
54-
cpy = count / BITS_PER_BYTE;
55-
set = (nfdt->max_fds - count) / BITS_PER_BYTE;
56-
memcpy(nfdt->open_fds, ofdt->open_fds, cpy);
57-
memset((char *)nfdt->open_fds + cpy, 0, set);
58-
memcpy(nfdt->close_on_exec, ofdt->close_on_exec, cpy);
59-
memset((char *)nfdt->close_on_exec + cpy, 0, set);
60-
61-
cpy = BITBIT_SIZE(count);
62-
set = BITBIT_SIZE(nfdt->max_fds) - cpy;
63-
memcpy(nfdt->full_fds_bits, ofdt->full_fds_bits, cpy);
64-
memset((char *)nfdt->full_fds_bits + cpy, 0, set);
53+
unsigned int nwords = fdt_words(nfdt);
54+
55+
bitmap_copy_and_extend(nfdt->open_fds, ofdt->open_fds,
56+
copy_words * BITS_PER_LONG, nwords * BITS_PER_LONG);
57+
bitmap_copy_and_extend(nfdt->close_on_exec, ofdt->close_on_exec,
58+
copy_words * BITS_PER_LONG, nwords * BITS_PER_LONG);
59+
bitmap_copy_and_extend(nfdt->full_fds_bits, ofdt->full_fds_bits,
60+
copy_words, nwords);
6561
}
6662

6763
/*
@@ -79,7 +75,7 @@ static void copy_fdtable(struct fdtable *nfdt, struct fdtable *ofdt)
7975
memcpy(nfdt->fd, ofdt->fd, cpy);
8076
memset((char *)nfdt->fd + cpy, 0, set);
8177

82-
copy_fd_bitmaps(nfdt, ofdt, ofdt->max_fds);
78+
copy_fd_bitmaps(nfdt, ofdt, fdt_words(ofdt));
8379
}
8480

8581
static struct fdtable * alloc_fdtable(unsigned int nr)
@@ -330,7 +326,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, int *errorp)
330326
open_files = count_open_files(old_fdt);
331327
}
332328

333-
copy_fd_bitmaps(new_fdt, old_fdt, open_files);
329+
copy_fd_bitmaps(new_fdt, old_fdt, open_files / BITS_PER_LONG);
334330

335331
old_fds = old_fdt->fd;
336332
new_fds = new_fdt->fd;

include/linux/bitmap.h

+12
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,18 @@ static inline void bitmap_copy_clear_tail(unsigned long *dst,
256256
dst[nbits / BITS_PER_LONG] &= BITMAP_LAST_WORD_MASK(nbits);
257257
}
258258

259+
static inline void bitmap_copy_and_extend(unsigned long *to,
260+
const unsigned long *from,
261+
unsigned int count, unsigned int size)
262+
{
263+
unsigned int copy = BITS_TO_LONGS(count);
264+
265+
memcpy(to, from, copy * sizeof(long));
266+
if (count % BITS_PER_LONG)
267+
to[copy - 1] &= BITMAP_LAST_WORD_MASK(count);
268+
memset(to + copy, 0, bitmap_size(size) - copy * sizeof(long));
269+
}
270+
259271
/*
260272
* On 32-bit systems bitmaps are represented as u32 arrays internally, and
261273
* therefore conversion is not needed when copying data from/to arrays of u32.

0 commit comments

Comments
 (0)