1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2020 SUSE 4 * 5 * Test transmitting data over a PTY/TTY line discipline and reading from the 6 * virtual netdev created by the line discipline. Also hangup the PTY while 7 * data is in flight to try to cause a race between the netdev being deleted 8 * and the discipline receive function writing to the netdev. 9 * 10 * For SLCAN we check stack data is not leaked in the frame padding 11 * (CVE-2020-11494). 12 * 13 * Test flow: 14 * 1. Create PTY with ldisc X which creates netdev Y 15 * 2. Open raw packet socket and bind to netdev Y 16 * 3. Send data on ptmx and read packets from socket 17 * 4. Hangup while transmission in progress 18 * 19 * Note that not all line disciplines call unthrottle when they are ready to 20 * read more bytes. So it is possible to fill all the write buffers causing 21 * write to block forever (because once write sleeps it needs unthrottle to 22 * wake it). So we write with O_NONBLOCK. 23 * 24 * Also the max buffer size for PTYs is 8192, so even if the protocol MTU is 25 * greater everything may still be processed in 8129 byte chunks. At least 26 * until we are in the netdev code which can have a bigger buffer. Of course 27 * the MTU still decides exactly where the packet delimiter goes, this just 28 * concerns choosing the best packet size to cause a race. 29 * 30 * Note on line discipline encapsulation formats: 31 * - For SLIP frames we just write the data followed by a delimiter char 32 * - SLCAN we write some ASCII described in drivers/net/can/slcan.c which is 33 * converted to the actual frame by the kernel 34 */ 35 36#define _GNU_SOURCE 37#include "config.h" 38#include "tst_test.h" 39#include "tst_buffers.h" 40#include "lapi/tty.h" 41 42#if defined(HAVE_LINUX_IF_PACKET_H) && defined(HAVE_LINUX_IF_ETHER_H) 43 44#include <linux/if_packet.h> 45#include <linux/if_ether.h> 46#include <linux/tty.h> 47 48/* 49 * define instead of including <linux/can.h> to support kernel headers 50 * before change from v4.2-rc1 51 * a2f11835994e ("can.h: make padding given by gcc explicit"). 52 */ 53 54#define CAN_MTU (sizeof(struct can_frame)) 55#define CAN_MAX_DLEN 8 56 57typedef uint32_t canid_t; 58 59struct can_frame { 60 canid_t can_id; 61 uint8_t can_dlc; 62 uint8_t __pad; 63 uint8_t __res0; 64 uint8_t __res1; 65 uint8_t data[CAN_MAX_DLEN] __attribute__((aligned(8))); 66}; 67 68#include <stddef.h> 69#include <stdlib.h> 70#include <stdio.h> 71#include <errno.h> 72#include <unistd.h> 73#include <termios.h> 74#include <sys/types.h> 75#include <sys/socket.h> 76#include <net/if.h> 77#include "lapi/ioctl.h" 78 79#include "tst_safe_stdio.h" 80 81#define SLCAN_FRAME "t00185f5f5f5f5f5f5f5f\r" 82 83struct ldisc_info { 84 int n; 85 char *name; 86 int mtu; 87}; 88 89static struct ldisc_info ldiscs[] = { 90 {N_SLIP, "N_SLIP", 8192}, 91 {N_SLCAN, "N_SLCAN", CAN_MTU}, 92}; 93 94static int ptmx = -1, pts = -1, sk = -1, mtu, no_check; 95 96static int set_ldisc(int tty, const struct ldisc_info *ldisc) 97{ 98 TEST(ioctl(tty, TIOCSETD, &ldisc->n)); 99 100 if (!TST_RET) 101 return 0; 102 103 if (TST_ERR == EINVAL) { 104 tst_res(TCONF | TTERRNO, 105 "You don't appear to have the %s TTY line discipline", 106 ldisc->name); 107 } else { 108 tst_res(TFAIL | TTERRNO, 109 "Failed to set the %s line discipline", ldisc->name); 110 } 111 112 return 1; 113} 114 115static int open_pty(const struct ldisc_info *ldisc) 116{ 117 char pts_path[PATH_MAX]; 118 119 ptmx = SAFE_OPEN("/dev/ptmx", O_RDWR); 120 if (grantpt(ptmx)) 121 tst_brk(TBROK | TERRNO, "grantpt(ptmx)"); 122 if (unlockpt(ptmx)) 123 tst_brk(TBROK | TERRNO, "unlockpt(ptmx)"); 124 if (ptsname_r(ptmx, pts_path, sizeof(pts_path))) 125 tst_brk(TBROK | TERRNO, "ptsname_r(ptmx, ...)"); 126 127 SAFE_FCNTL(ptmx, F_SETFL, O_NONBLOCK); 128 129 tst_res(TINFO, "PTS path is %s", pts_path); 130 pts = SAFE_OPEN(pts_path, O_RDWR); 131 132 return set_ldisc(pts, ldisc); 133} 134 135static ssize_t try_async_write(int fd, const char *data, ssize_t size, 136 ssize_t *done) 137{ 138 ssize_t off = done ? *done : 0; 139 ssize_t ret = write(fd, data + off, size - off); 140 141 if (ret < 0) 142 return -(errno != EAGAIN); 143 144 if (!done) 145 return 1; 146 147 *done += ret; 148 return *done >= size; 149} 150 151static ssize_t try_async_read(int fd, char *data, ssize_t size, 152 ssize_t *done) 153{ 154 ssize_t off = done ? *done : 0; 155 ssize_t ret = read(fd, data + off, size - off); 156 157 if (ret < 0) 158 return -(errno != EAGAIN); 159 160 if (!done) 161 return 1; 162 163 *done += ret; 164 return *done >= size; 165} 166 167static ssize_t retry_async_write(int fd, const char *data, ssize_t size) 168{ 169 ssize_t done = 0; 170 171 return TST_RETRY_FUNC(try_async_write(fd, data, size, &done), 172 TST_RETVAL_NOTNULL); 173} 174 175static ssize_t retry_async_read(int fd, char *data, ssize_t size) 176{ 177 ssize_t done = 0; 178 179 return TST_RETRY_FUNC(try_async_read(fd, data, size, &done), 180 TST_RETVAL_NOTNULL); 181} 182 183static void do_pty(const struct ldisc_info *ldisc) 184{ 185 char *data; 186 ssize_t ret; 187 size_t len = 0; 188 189 switch (ldisc->n) { 190 case N_SLIP: 191 len = mtu; 192 break; 193 case N_SLCAN: 194 len = sizeof(SLCAN_FRAME) - 1; 195 break; 196 } 197 198 data = tst_alloc(len); 199 200 switch (ldisc->n) { 201 case N_SLIP: 202 memset(data, '_', len - 1); 203 data[len - 1] = 0300; 204 break; 205 case N_SLCAN: 206 memcpy(data, SLCAN_FRAME, len); 207 break; 208 } 209 210 ret = retry_async_write(ptmx, data, len); 211 if (ret < 0) 212 tst_brk(TBROK | TERRNO, "Failed 1st write to PTY"); 213 tst_res(TPASS, "Wrote PTY %s %d (1)", ldisc->name, ptmx); 214 215 ret = retry_async_write(ptmx, data, len); 216 if (ret < 0) 217 tst_brk(TBROK | TERRNO, "Failed 2nd write to PTY"); 218 219 if (tcflush(ptmx, TCIFLUSH)) 220 tst_brk(TBROK | TERRNO, "tcflush(ptmx, TCIFLUSH)"); 221 222 tst_res(TPASS, "Wrote PTY %s %d (2)", ldisc->name, ptmx); 223 224 ret = retry_async_read(ptmx, data, len); 225 if (ret < 0) 226 tst_brk(TBROK | TERRNO, "Failed read of PTY"); 227 228 tst_res(TPASS, "Read PTY %s %d", ldisc->name, ptmx); 229 TST_CHECKPOINT_WAKE(0); 230 231 while (1) { 232 if (retry_async_read(ptmx, data, len) < 0) 233 break; 234 235 if (retry_async_write(ptmx, data, len) < 0) 236 break; 237 } 238 239 tst_res(TPASS, "Transmission on PTY interrupted by hangup"); 240 241 tst_free_all(); 242} 243 244static void open_netdev(const struct ldisc_info *ldisc) 245{ 246 struct ifreq ifreq = { 0 }; 247 struct sockaddr_ll lla = { 0 }; 248 249 SAFE_IOCTL(pts, SIOCGIFNAME, ifreq.ifr_name); 250 tst_res(TINFO, "Netdev is %s", ifreq.ifr_name); 251 252 sk = SAFE_SOCKET(PF_PACKET, SOCK_RAW, 0); 253 254 ifreq.ifr_mtu = ldisc->mtu; 255 if (ioctl(sk, SIOCSIFMTU, &ifreq)) 256 tst_res(TWARN | TERRNO, "Failed to set netdev MTU to maximum"); 257 SAFE_IOCTL(sk, SIOCGIFMTU, &ifreq); 258 mtu = ifreq.ifr_mtu; 259 tst_res(TINFO, "Netdev MTU is %d (we set %d)", mtu, ldisc->mtu); 260 261 SAFE_IOCTL(sk, SIOCGIFFLAGS, &ifreq); 262 ifreq.ifr_flags |= IFF_UP | IFF_RUNNING; 263 SAFE_IOCTL(sk, SIOCSIFFLAGS, &ifreq); 264 SAFE_IOCTL(sk, SIOCGIFFLAGS, &ifreq); 265 266 if (!(ifreq.ifr_flags & IFF_UP)) 267 tst_brk(TBROK, "Netdev did not come up"); 268 269 SAFE_IOCTL(sk, SIOCGIFINDEX, &ifreq); 270 271 lla.sll_family = PF_PACKET; 272 lla.sll_protocol = htons(ETH_P_ALL); 273 lla.sll_ifindex = ifreq.ifr_ifindex; 274 SAFE_BIND(sk, (struct sockaddr *)&lla, sizeof(struct sockaddr_ll)); 275 276 tst_res(TINFO, "Bound netdev %d to socket %d", ifreq.ifr_ifindex, sk); 277} 278 279static void check_data(const struct ldisc_info *ldisc, 280 const char *data, ssize_t len) 281{ 282 ssize_t i = 0, j; 283 struct can_frame frm; 284 285 if (no_check) 286 return; 287 288 if (ldisc->n == N_SLCAN) { 289 memcpy(&frm, data, len); 290 291 if (frm.can_id != 1) { 292 tst_res(TFAIL, "can_id = %d != 1", 293 frm.can_id); 294 no_check = 1; 295 } 296 297 if (frm.can_dlc != CAN_MAX_DLEN) { 298 tst_res(TFAIL, "can_dlc = %d != " TST_TO_STR_(CAN_MAX_DLEN), 299 frm.can_dlc); 300 no_check = 1; 301 } 302 303 i = offsetof(struct can_frame, __pad); 304 if (frm.__pad != frm.__res0 || frm.__res0 != frm.__res1) { 305 tst_res_hexd(TFAIL, data + i, 306 offsetof(struct can_frame, data) - i, 307 "Padding bytes may contain stack data"); 308 no_check = 1; 309 } 310 311 i = offsetof(struct can_frame, data); 312 } 313 314 do { 315 if (i >= len) 316 return; 317 } while (data[i++] == '_'); 318 319 j = i--; 320 321 while (j < len && j - i < 65 && data[j++] != '_') 322 ; 323 j--; 324 325 tst_res_hexd(TFAIL, data + i, j - i, 326 "Corrupt data (max 64 of %ld bytes shown): data[%ld..%ld] = ", 327 len, i, j); 328 no_check = 1; 329 330 if (no_check) 331 tst_res(TINFO, "Will continue test without data checking"); 332} 333 334static ssize_t try_sync_read(int fd, char *data, ssize_t size) 335{ 336 ssize_t ret, n = 0; 337 int retry = mtu; 338 339 while (retry--) { 340 ret = read(fd, data + n, size - n); 341 342 if (ret < 0) 343 return ret; 344 345 if ((n += ret) >= size) 346 return ret; 347 } 348 349 tst_brk(TBROK | TERRNO, "Only read %zd of %zd bytes", n, size); 350 351 return n; 352} 353 354static ssize_t try_sync_write(int fd, const char *data, ssize_t size) 355{ 356 ssize_t ret, n = 0; 357 int retry = mtu; 358 359 while (retry--) { 360 ret = write(fd, data + n, size - n); 361 362 if (ret < 0) 363 return ret; 364 365 if ((n += ret) >= size) 366 return ret; 367 } 368 369 tst_brk(TBROK | TERRNO, "Only wrote %zd of %zd bytes", n, size); 370 371 return n; 372} 373 374static void read_netdev(const struct ldisc_info *ldisc) 375{ 376 int rlen, plen = 0; 377 char *data; 378 379 switch (ldisc->n) { 380 case N_SLIP: 381 plen = mtu - 1; 382 break; 383 case N_SLCAN: 384 plen = CAN_MTU; 385 break; 386 } 387 data = tst_alloc(plen); 388 389 tst_res(TINFO, "Reading from socket %d", sk); 390 391 TEST(try_sync_read(sk, data, plen)); 392 if (TST_RET < 0) 393 tst_brk(TBROK | TTERRNO, "Read netdev %s %d (1)", ldisc->name, sk); 394 check_data(ldisc, data, plen); 395 tst_res(TPASS, "Read netdev %s %d (1)", ldisc->name, sk); 396 397 TEST(try_sync_read(sk, data, plen)); 398 if (TST_RET < 0) 399 tst_brk(TBROK | TTERRNO, "Read netdev %s %d (2)", ldisc->name, sk); 400 check_data(ldisc, data, plen); 401 tst_res(TPASS, "Read netdev %s %d (2)", ldisc->name, sk); 402 403 TEST(try_sync_write(sk, data, plen)); 404 if (TST_RET < 0) 405 tst_brk(TBROK | TTERRNO, "Write netdev %s %d", ldisc->name, sk); 406 407 tst_res(TPASS, "Write netdev %s %d", ldisc->name, sk); 408 409 while (1) { 410 if (try_sync_write(sk, data, plen) < 0) 411 break; 412 413 if ((rlen = try_sync_read(sk, data, plen)) < 0) 414 break; 415 check_data(ldisc, data, rlen); 416 } 417 418 tst_res(TPASS, "Data transmission on netdev interrupted by hangup"); 419 420 close(sk); 421 tst_free_all(); 422} 423 424static void do_test(unsigned int n) 425{ 426 struct ldisc_info *ldisc = &ldiscs[n]; 427 428 if (open_pty(ldisc)) 429 return; 430 431 open_netdev(ldisc); 432 433 if (!SAFE_FORK()) { 434 read_netdev(ldisc); 435 return; 436 } 437 438 if (!SAFE_FORK()) { 439 do_pty(ldisc); 440 return; 441 } 442 443 if (!SAFE_FORK()) { 444 TST_CHECKPOINT_WAIT2(0, 100000); 445 SAFE_IOCTL(pts, TIOCVHANGUP); 446 tst_res(TINFO, "Sent hangup ioctl to PTS"); 447 SAFE_IOCTL(ptmx, TIOCVHANGUP); 448 tst_res(TINFO, "Sent hangup ioctl to PTM"); 449 return; 450 } 451 452 tst_reap_children(); 453} 454 455static void cleanup(void) 456{ 457 if (pts >= 0) 458 ioctl(pts, TIOCVHANGUP); 459 460 if (ptmx >= 0) 461 ioctl(ptmx, TIOCVHANGUP); 462 463 if (sk >= 0) 464 close(sk); 465 466 tst_reap_children(); 467} 468 469static struct tst_test test = { 470 .test = do_test, 471 .cleanup = cleanup, 472 .tcnt = 2, 473 .forks_child = 1, 474 .needs_checkpoints = 1, 475 .needs_root = 1, 476 .tags = (const struct tst_tag[]){ 477 {"linux-git", "b9258a2cece4ec1f020715fe3554bc2e360f6264"}, 478 {"CVE", "CVE-2020-11494"}, 479 {} 480 } 481}; 482 483#else 484 485TST_TEST_TCONF("Need <linux/if_packet.h> and <linux/if_ether.h>"); 486 487#endif 488