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 (TST_RET == -1 && TST_ERR != EFAULT) {
75 tst_res(TFAIL | TTERRNO, "unexpected errno");
76 SAFE_CLOSE(fd);
77 return;
78 }
79
80 /* nothing has been written */
81 if (TST_RET == -1 && TST_ERR == 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", TST_RET);
104 if (TST_RET > (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, TST_RET);
113 if (memcmp(tmp, wr_iovec[0].iov_base, TST_RET) == 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, TST_RET,
118 "expected:");
119 tst_res_hexd(TFAIL, tmp, TST_RET,
120 "actual file content:");
121 }
122
123 /* file offset has been updated according to written bytes */
124 if (off_after == initial_file_offset + TST_RET)
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