1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2015 Fujitsu Ltd. Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com>
4 * Copyright (C) 2021 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
5 */
6
7 /*\
8 * [Description]
9 *
10 * This is a regression test for a silent data corruption for a mmaped file
11 * when filesystem gets out of space.
12 *
13 * Fixed by commits:
14 *
15 * commit 0572639ff66dcffe62d37adfe4c4576f9fc398f4
16 * Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com>
17 * Date: Thu Feb 12 23:00:17 2015 -0500
18 *
19 * ext4: fix mmap data corruption in nodelalloc mode when blocksize < pagesize
20 *
21 * commit d6320cbfc92910a3e5f10c42d98c231c98db4f60
22 * Author: Jan Kara <jack@suse.cz>
23 * Date: Wed Oct 1 21:49:46 2014 -0400
24 *
25 * ext4: fix mmap data corruption when blocksize < pagesize
26 */
27
28 #define _GNU_SOURCE
29
30 #include <stdlib.h>
31 #include <signal.h>
32 #include <time.h>
33 #include <sys/mman.h>
34 #include <sys/wait.h>
35 #include "tst_test.h"
36
37 #define MNTPOINT "mntpoint"
38 #define FILE_PARENT "mntpoint/testfilep"
39 #define FILE_CHILD "mntpoint/testfilec"
40 #define FS_BLOCKSIZE 1024
41 #define LOOPS 10
42
43 static int parentfd = -1;
44 static int childfd = -1;
45
do_child(void)46 static void do_child(void)
47 {
48 int offset;
49 int page_size;
50 char buf[FS_BLOCKSIZE];
51 char *addr = NULL;
52
53 page_size = getpagesize();
54
55 childfd = SAFE_OPEN(FILE_CHILD, O_RDWR | O_CREAT, 0666);
56
57 memset(buf, 'a', FS_BLOCKSIZE);
58 SAFE_WRITE(SAFE_WRITE_ALL, childfd, buf, FS_BLOCKSIZE);
59
60 /*
61 * In case mremap() may fail because that memory area can not be
62 * expanded at current virtual address(MREMAP_MAYMOVE is not set),
63 * we first do a mmap(page_size * 2) operation to reserve some
64 * free address space.
65 */
66 addr = SAFE_MMAP(NULL, page_size * 2, PROT_WRITE | PROT_READ,
67 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
68 SAFE_MUNMAP(addr, page_size * 2);
69
70 addr = SAFE_MMAP(addr, FS_BLOCKSIZE, PROT_WRITE | PROT_READ, MAP_SHARED, childfd, 0);
71
72 addr[0] = 'a';
73
74 SAFE_FTRUNCATE(childfd, page_size * 2);
75
76 addr = mremap(addr, FS_BLOCKSIZE, 2 * page_size, 0);
77 if (addr == MAP_FAILED)
78 tst_brk(TBROK | TERRNO, "mremap failed unexpectedly");
79
80 /*
81 * Let parent process consume FS free blocks as many as possible, then
82 * there'll be no free blocks allocated for this new file mmaping for
83 * offset starting at 1024, 2048, or 3072. If this above kernel bug
84 * has been fixed, usually child process will killed by SIGBUS signal,
85 * if not, the data 'A', 'B', 'C' will be silently discarded later when
86 * kernel writepage is called, that means data corruption.
87 */
88 TST_CHECKPOINT_WAKE_AND_WAIT(0);
89
90 for (offset = FS_BLOCKSIZE; offset < page_size; offset += FS_BLOCKSIZE)
91 addr[offset] = 'a';
92
93 SAFE_MUNMAP(addr, 2 * page_size);
94 SAFE_CLOSE(childfd);
95
96 exit(1);
97 }
98
run_single(void)99 static void run_single(void)
100 {
101 int ret, status;
102 pid_t child;
103 char buf[FS_BLOCKSIZE];
104 int bug_reproduced = 0;
105
106 child = SAFE_FORK();
107 if (!child) {
108 do_child();
109 return;
110 }
111
112 parentfd = SAFE_OPEN(FILE_PARENT, O_RDWR | O_CREAT, 0666);
113
114 memset(buf, 'a', FS_BLOCKSIZE);
115
116 TST_CHECKPOINT_WAIT(0);
117
118 while (1) {
119 ret = write(parentfd, buf, FS_BLOCKSIZE);
120 if (ret < 0) {
121 if (errno == ENOSPC)
122 break;
123
124 tst_brk(TBROK | TERRNO, "write failed unexpectedly");
125 }
126 }
127
128 SAFE_CLOSE(parentfd);
129
130 TST_CHECKPOINT_WAKE(0);
131
132 SAFE_WAITPID(child, &status, 0);
133 if (WIFEXITED(status) && WEXITSTATUS(status) == 1) {
134 bug_reproduced = 1;
135 } else {
136 /*
137 * If child process was killed by SIGBUS, bug is not reproduced.
138 */
139 if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS) {
140 tst_brk(TBROK | TERRNO, "child process terminate unexpectedly with status %s",
141 tst_strstatus(status));
142 }
143 }
144
145 SAFE_UNLINK(FILE_PARENT);
146 SAFE_UNLINK(FILE_CHILD);
147
148 if (bug_reproduced)
149 tst_res(TFAIL, "bug is reproduced");
150 else
151 tst_res(TPASS, "bug is not reproduced");
152 }
153
run(void)154 static void run(void)
155 {
156 int i;
157
158 for (i = 0; i < LOOPS; i++)
159 run_single();
160 }
161
cleanup(void)162 static void cleanup(void)
163 {
164 if (childfd >= 0)
165 SAFE_CLOSE(childfd);
166
167 if (parentfd >= 0)
168 SAFE_CLOSE(parentfd);
169 }
170
171 static struct tst_test test = {
172 .test_all = run,
173 .cleanup = cleanup,
174 .forks_child = 1,
175 .needs_root = 1,
176 .needs_checkpoints = 1,
177 .mount_device = 1,
178 .mntpoint = MNTPOINT,
179 .dev_fs_type = "ext4",
180 .dev_fs_opts = (const char *const[]){
181 "-b",
182 "1024",
183 NULL,
184 },
185 .dev_extra_opts = (const char *const[]){
186 "10240",
187 NULL,
188 },
189 .needs_cmds = (const char *const[]){
190 "mkfs.ext4",
191 NULL,
192 },
193 .tags = (const struct tst_tag[]){
194 {"linux-git", "d6320cbfc929"},
195 {"linux-git", "0572639ff66d"},
196 {},
197 },
198 };
199