1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *
4  *   Copyright (c) Linux Test Project, 2016
5  */
6 
7 /*
8  * Test Description:
9  *   Verify writev() behaviour with partially valid iovec list.
10  *   Kernel <4.8 used to shorten write up to first bad invalid
11  *   iovec. Starting with 4.8, a writev with short data (under
12  *   page size) is likely to get shorten to 0 bytes and return
13  *   EFAULT.
14  *
15  *   This test doesn't make assumptions how much will write get
16  *   shortened. It only tests that file content/offset after
17  *   syscall corresponds to return value of writev().
18  *
19  *   See: [RFC] writev() semantics with invalid iovec in the middle
20  *        https://marc.info/?l=linux-kernel&m=147388891614289&w=2
21  */
22 
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdio.h>
26 #include <sys/mman.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/uio.h>
30 #include "tst_test.h"
31 
32 #define TESTFILE "testfile"
33 #define CHUNK 64
34 #define BUFSIZE (CHUNK * 4)
35 
36 static void *bad_addr;
37 
test_partially_valid_iovec(int initial_file_offset)38 static void test_partially_valid_iovec(int initial_file_offset)
39 {
40 	int i, fd;
41 	unsigned char buffer[BUFSIZE], fpattern[BUFSIZE], tmp[BUFSIZE];
42 	long off_after;
43 	struct iovec wr_iovec[] = {
44 		{ buffer, CHUNK },
45 		{ bad_addr, CHUNK },
46 		{ buffer + CHUNK, CHUNK },
47 		{ buffer + CHUNK * 2, CHUNK },
48 	};
49 
50 	tst_res(TINFO, "starting test with initial file offset: %d ",
51 		initial_file_offset);
52 
53 	for (i = 0; i < BUFSIZE; i++)
54 		buffer[i] = i % (CHUNK - 1);
55 
56 	memset(fpattern, 0xff, BUFSIZE);
57 	tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK);
58 
59 	fd = SAFE_OPEN(TESTFILE, O_RDWR, 0644);
60 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
61 	TEST(writev(fd, wr_iovec, ARRAY_SIZE(wr_iovec)));
62 	off_after = (long) SAFE_LSEEK(fd, 0, SEEK_CUR);
63 
64 	/* bad errno */
65 	if (TST_RET == -1 && TST_ERR != EFAULT) {
66 		tst_res(TFAIL | TTERRNO, "unexpected errno");
67 		SAFE_CLOSE(fd);
68 		return;
69 	}
70 
71 	/* nothing has been written */
72 	if (TST_RET == -1 && TST_ERR == EFAULT) {
73 		tst_res(TINFO, "got EFAULT");
74 		/* initial file content remains untouched */
75 		SAFE_LSEEK(fd, 0, SEEK_SET);
76 		SAFE_READ(1, fd, tmp, BUFSIZE);
77 		if (memcmp(tmp, fpattern, BUFSIZE))
78 			tst_res(TFAIL, "file was written to");
79 		else
80 			tst_res(TPASS, "file stayed untouched");
81 
82 		/* offset hasn't changed */
83 		if (off_after == initial_file_offset)
84 			tst_res(TPASS, "offset stayed unchanged");
85 		else
86 			tst_res(TFAIL, "offset changed to %ld",
87 				off_after);
88 
89 		SAFE_CLOSE(fd);
90 		return;
91 	}
92 
93 	/* writev() wrote more bytes than bytes preceding invalid iovec */
94 	tst_res(TINFO, "writev() has written %ld bytes", TST_RET);
95 	if (TST_RET > (long) wr_iovec[0].iov_len) {
96 		tst_res(TFAIL, "writev wrote more than expected");
97 		SAFE_CLOSE(fd);
98 		return;
99 	}
100 
101 	/* file content matches written bytes */
102 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
103 	SAFE_READ(1, fd, tmp, TST_RET);
104 	if (memcmp(tmp, wr_iovec[0].iov_base, TST_RET) == 0) {
105 		tst_res(TPASS, "file has expected content");
106 	} else {
107 		tst_res(TFAIL, "file has unexpected content");
108 		tst_res_hexd(TFAIL, wr_iovec[0].iov_base, TST_RET,
109 				"expected:");
110 		tst_res_hexd(TFAIL, tmp, TST_RET,
111 				"actual file content:");
112 	}
113 
114 	/* file offset has been updated according to written bytes */
115 	if (off_after == initial_file_offset + TST_RET)
116 		tst_res(TPASS, "offset at %ld as expected", off_after);
117 	else
118 		tst_res(TFAIL, "offset unexpected %ld", off_after);
119 
120 	SAFE_CLOSE(fd);
121 }
122 
test_writev(void)123 static void test_writev(void)
124 {
125 	test_partially_valid_iovec(0);
126 	test_partially_valid_iovec(CHUNK + 1);
127 	test_partially_valid_iovec(getpagesize());
128 	test_partially_valid_iovec(getpagesize() + 1);
129 }
130 
setup(void)131 static void setup(void)
132 {
133 	bad_addr = SAFE_MMAP(0, 1, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS,
134 			0, 0);
135 }
136 
137 static struct tst_test test = {
138 	.needs_tmpdir = 1,
139 	.setup = setup,
140 	.test_all = test_writev,
141 };
142