• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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