The block limits may be read while they are being modified. The statement "q->limits = *lim" is not really atomic. The compiler may turn it into memcpy (clang does). On x86-64, the kernel uses the "rep movsb" instruction for memcpy - it is optimized on modern CPUs, but it is not atomic, it may be interrupted at any byte boundary - and if it is interrupted, the readers may read garbage. On sparc64, there's an instruction that zeroes a cache line without reading it from memory. The kernel memcpy implementation uses it (see b3a04ed507bf) to avoid loading the destination buffer from memory. The problem is that if we copy a block of data to q->limits and someone reads it at the same time, the reader may read zeros. This commit changes it to use WRITE_ONCE, so that individual words are updated atomically. Signed-off-by: Mikulas Patocka <mpatocka@xxxxxxxxxx> Cc: stable@xxxxxxxxxxxxxxx --- block/blk-settings.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) Index: linux-2.6/block/blk-settings.c =================================================================== --- linux-2.6.orig/block/blk-settings.c +++ linux-2.6/block/blk-settings.c @@ -433,6 +433,7 @@ int queue_limits_commit_update(struct re struct queue_limits *lim) { int error; + size_t i; error = blk_validate_limits(lim); if (error) @@ -446,7 +447,14 @@ int queue_limits_commit_update(struct re } #endif - q->limits = *lim; + /* + * Note that direct assignment like "q->limits = *lim" is not atomic + * (the compiler can generate things like "rep movsb" for it), + * so we use WRITE_ONCE. + */ + for (i = 0; i < sizeof(struct queue_limits) / sizeof(long); i++) + WRITE_ONCE(*((long *)&q->limits + i), *((long *)lim + i)); + if (q->disk) blk_apply_bdi_limits(q->disk->bdi, lim); out_unlock: