Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756380AbcJZRFc (ORCPT ); Wed, 26 Oct 2016 13:05:32 -0400 Received: from mail-wm0-f42.google.com ([74.125.82.42]:36416 "EHLO mail-wm0-f42.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755765AbcJZRF0 (ORCPT ); Wed, 26 Oct 2016 13:05:26 -0400 To: Kernel Mailing List From: Jakob Unterwurzacher Subject: tmpfs returns incorrect data on concurrent pread() and truncate() Message-ID: <18e9fa0f-ec31-9107-459c-ae1694503f87@gmail.com> Date: Wed, 26 Oct 2016 19:05:22 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 2417 Lines: 113 tmpfs seems to be incorrectly returning 0-bytes when reading from a file that is concurrently being truncated. This is causing crashes in gocryptfs, a cryptographic FUSE overlay, when it reads a nonce from disk that should absolutely positively never be all-zero. I have written a reproducer in C that triggers this issue on both boxes I tested, Linux 4.7.2 and 3.16.7, both amd64. It can be downloaded from here: https://gist.github.com/rfjakob/d01281c737db38075767f90bf03fc475 or, alternatively, I have attached it to this email at the bottom. The reproducer: 1) Creates a 10MB file filled with 'x' at /dev/shm/x 2) Spawns a thread that truncates the file 3 bytes at a time 3) Spawns another thread that pread()s the file 1 byte at a time starting from the top 4) Prints "wrong data" whenever the pread() gets something that is not 'x' or an empty result. Example run: $ gcc -Wall -lpthread truncate_read.c && ./a.out wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 wrong data: 0 [...] Best regards, Jakob --------------------------------------------------------------------- truncate_read.c ------------------------------8<--------------------------------------- // Compile and run: // gcc -Wall -lpthread truncate_read.c && ./a.out #include #include #include #include #include #include #include #include int fd; int l = 10*1024*1024; pthread_t tid[2]; void* read_thread(void *arg){ int o,n; char b; for(o=l; o>0; o--) { b = 'a'; n = pread(fd, &b, 1, o); if(n==0) { continue; } if(b != 'x') { printf("wrong data: %x\n", b); } } return NULL; } void* truncate_thread(void *arg){ // Parent = Truncater int o,n; // "3" seems to be the sweet spot to trigger the most errors. for(o=l; o>0; o-=3) { n = ftruncate(fd, o); if(n!=0) { perror("ftruncate err"); } } return NULL; } int main(int argc, char *argv[]) { fd = open("/dev/shm/x", O_RDWR | O_TRUNC | O_CREAT, 0666); if(fd < 0) { printf("open failed\n"); exit(1); } char* x = malloc(l); memset(x, 'x', l); write(fd, x, l); pthread_create(&(tid[0]), NULL, &read_thread, NULL); pthread_create(&(tid[1]), NULL, &truncate_thread, NULL); void *res; pthread_join(tid[0], &res); pthread_join(tid[1], &res); return 0; }