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