1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <sched.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <sys/capability.h> 11 #include <sys/ioctl.h> 12 #include <sys/stat.h> 13 #include <sys/syscall.h> 14 #include <sys/types.h> 15 #include <sys/wait.h> 16 #include <unistd.h> 17 #include <linux/nsfs.h> 18 #include "../kselftest_harness.h" 19 #include "../filesystems/utils.h" 20 #include "wrappers.h" 21 22 /* 23 * Test credential changes and their impact on namespace active references. 24 */ 25 26 /* 27 * Test setuid() in a user namespace properly swaps active references. 28 * Create a user namespace with multiple UIDs mapped, then setuid() between them. 29 * Verify that the user namespace remains active throughout. 30 */ 31 TEST(setuid_preserves_active_refs) 32 { 33 pid_t pid; 34 int status; 35 __u64 userns_id; 36 struct ns_id_req req = { 37 .size = sizeof(req), 38 .spare = 0, 39 .ns_id = 0, 40 .ns_type = CLONE_NEWUSER, 41 .spare2 = 0, 42 .user_ns_id = 0, 43 }; 44 __u64 ns_ids[256]; 45 ssize_t ret; 46 int i; 47 bool found = false; 48 int pipefd[2]; 49 50 ASSERT_EQ(pipe(pipefd), 0); 51 52 pid = fork(); 53 ASSERT_GE(pid, 0); 54 55 if (pid == 0) { 56 /* Child process */ 57 int fd, userns_fd; 58 __u64 child_userns_id; 59 uid_t orig_uid = getuid(); 60 int setuid_count; 61 62 close(pipefd[0]); 63 64 /* Create new user namespace with multiple UIDs mapped (0-9) */ 65 userns_fd = get_userns_fd(0, orig_uid, 10); 66 if (userns_fd < 0) { 67 close(pipefd[1]); 68 exit(1); 69 } 70 71 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 72 close(userns_fd); 73 close(pipefd[1]); 74 exit(1); 75 } 76 close(userns_fd); 77 78 /* Get user namespace ID */ 79 fd = open("/proc/self/ns/user", O_RDONLY); 80 if (fd < 0) { 81 close(pipefd[1]); 82 exit(1); 83 } 84 85 if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) { 86 close(fd); 87 close(pipefd[1]); 88 exit(1); 89 } 90 close(fd); 91 92 /* Send namespace ID to parent */ 93 write(pipefd[1], &child_userns_id, sizeof(child_userns_id)); 94 95 /* 96 * Perform multiple setuid() calls. 97 * Each setuid() triggers commit_creds() which should properly 98 * swap active references via switch_cred_namespaces(). 99 */ 100 for (setuid_count = 0; setuid_count < 50; setuid_count++) { 101 uid_t target_uid = (setuid_count % 10); 102 if (setuid(target_uid) < 0) { 103 if (errno != EPERM) { 104 close(pipefd[1]); 105 exit(1); 106 } 107 } 108 } 109 110 close(pipefd[1]); 111 exit(0); 112 } 113 114 /* Parent process */ 115 close(pipefd[1]); 116 117 if (read(pipefd[0], &userns_id, sizeof(userns_id)) != sizeof(userns_id)) { 118 close(pipefd[0]); 119 kill(pid, SIGKILL); 120 waitpid(pid, NULL, 0); 121 SKIP(return, "Failed to get namespace ID from child"); 122 } 123 close(pipefd[0]); 124 125 TH_LOG("Child user namespace ID: %llu", (unsigned long long)userns_id); 126 127 /* Verify namespace is active while child is running */ 128 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 129 if (ret < 0) { 130 kill(pid, SIGKILL); 131 waitpid(pid, NULL, 0); 132 if (errno == ENOSYS) 133 SKIP(return, "listns() not supported"); 134 ASSERT_GE(ret, 0); 135 } 136 137 for (i = 0; i < ret; i++) { 138 if (ns_ids[i] == userns_id) { 139 found = true; 140 break; 141 } 142 } 143 ASSERT_TRUE(found); 144 145 waitpid(pid, &status, 0); 146 ASSERT_TRUE(WIFEXITED(status)); 147 ASSERT_EQ(WEXITSTATUS(status), 0); 148 149 /* Verify namespace becomes inactive after child exits */ 150 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 151 ASSERT_GE(ret, 0); 152 153 found = false; 154 for (i = 0; i < ret; i++) { 155 if (ns_ids[i] == userns_id) { 156 found = true; 157 break; 158 } 159 } 160 161 ASSERT_FALSE(found); 162 TH_LOG("setuid() correctly preserved active references (no leak)"); 163 } 164 165 /* 166 * Test setgid() in a user namespace properly handles active references. 167 */ 168 TEST(setgid_preserves_active_refs) 169 { 170 pid_t pid; 171 int status; 172 __u64 userns_id; 173 struct ns_id_req req = { 174 .size = sizeof(req), 175 .spare = 0, 176 .ns_id = 0, 177 .ns_type = CLONE_NEWUSER, 178 .spare2 = 0, 179 .user_ns_id = 0, 180 }; 181 __u64 ns_ids[256]; 182 ssize_t ret; 183 int i; 184 bool found = false; 185 int pipefd[2]; 186 187 ASSERT_EQ(pipe(pipefd), 0); 188 189 pid = fork(); 190 ASSERT_GE(pid, 0); 191 192 if (pid == 0) { 193 /* Child process */ 194 int fd, userns_fd; 195 __u64 child_userns_id; 196 uid_t orig_uid = getuid(); 197 int setgid_count; 198 199 close(pipefd[0]); 200 201 /* Create new user namespace with multiple GIDs mapped */ 202 userns_fd = get_userns_fd(0, orig_uid, 10); 203 if (userns_fd < 0) { 204 close(pipefd[1]); 205 exit(1); 206 } 207 208 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 209 close(userns_fd); 210 close(pipefd[1]); 211 exit(1); 212 } 213 close(userns_fd); 214 215 /* Get user namespace ID */ 216 fd = open("/proc/self/ns/user", O_RDONLY); 217 if (fd < 0) { 218 close(pipefd[1]); 219 exit(1); 220 } 221 222 if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) { 223 close(fd); 224 close(pipefd[1]); 225 exit(1); 226 } 227 close(fd); 228 229 write(pipefd[1], &child_userns_id, sizeof(child_userns_id)); 230 231 /* Perform multiple setgid() calls */ 232 for (setgid_count = 0; setgid_count < 50; setgid_count++) { 233 gid_t target_gid = (setgid_count % 10); 234 if (setgid(target_gid) < 0) { 235 if (errno != EPERM) { 236 close(pipefd[1]); 237 exit(1); 238 } 239 } 240 } 241 242 close(pipefd[1]); 243 exit(0); 244 } 245 246 /* Parent process */ 247 close(pipefd[1]); 248 249 if (read(pipefd[0], &userns_id, sizeof(userns_id)) != sizeof(userns_id)) { 250 close(pipefd[0]); 251 kill(pid, SIGKILL); 252 waitpid(pid, NULL, 0); 253 SKIP(return, "Failed to get namespace ID from child"); 254 } 255 close(pipefd[0]); 256 257 waitpid(pid, &status, 0); 258 ASSERT_TRUE(WIFEXITED(status)); 259 ASSERT_EQ(WEXITSTATUS(status), 0); 260 261 /* Verify namespace becomes inactive */ 262 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 263 if (ret < 0) { 264 if (errno == ENOSYS) 265 SKIP(return, "listns() not supported"); 266 ASSERT_GE(ret, 0); 267 } 268 269 for (i = 0; i < ret; i++) { 270 if (ns_ids[i] == userns_id) { 271 found = true; 272 break; 273 } 274 } 275 276 ASSERT_FALSE(found); 277 TH_LOG("setgid() correctly preserved active references (no leak)"); 278 } 279 280 /* 281 * Test setresuid() which changes real, effective, and saved UIDs. 282 * This should properly swap active references via commit_creds(). 283 */ 284 TEST(setresuid_preserves_active_refs) 285 { 286 pid_t pid; 287 int status; 288 __u64 userns_id; 289 struct ns_id_req req = { 290 .size = sizeof(req), 291 .spare = 0, 292 .ns_id = 0, 293 .ns_type = CLONE_NEWUSER, 294 .spare2 = 0, 295 .user_ns_id = 0, 296 }; 297 __u64 ns_ids[256]; 298 ssize_t ret; 299 int i; 300 bool found = false; 301 int pipefd[2]; 302 303 ASSERT_EQ(pipe(pipefd), 0); 304 305 pid = fork(); 306 ASSERT_GE(pid, 0); 307 308 if (pid == 0) { 309 /* Child process */ 310 int fd, userns_fd; 311 __u64 child_userns_id; 312 uid_t orig_uid = getuid(); 313 int setres_count; 314 315 close(pipefd[0]); 316 317 /* Create new user namespace */ 318 userns_fd = get_userns_fd(0, orig_uid, 10); 319 if (userns_fd < 0) { 320 close(pipefd[1]); 321 exit(1); 322 } 323 324 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 325 close(userns_fd); 326 close(pipefd[1]); 327 exit(1); 328 } 329 close(userns_fd); 330 331 /* Get user namespace ID */ 332 fd = open("/proc/self/ns/user", O_RDONLY); 333 if (fd < 0) { 334 close(pipefd[1]); 335 exit(1); 336 } 337 338 if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) { 339 close(fd); 340 close(pipefd[1]); 341 exit(1); 342 } 343 close(fd); 344 345 write(pipefd[1], &child_userns_id, sizeof(child_userns_id)); 346 347 /* Perform multiple setresuid() calls */ 348 for (setres_count = 0; setres_count < 30; setres_count++) { 349 uid_t uid1 = (setres_count % 5); 350 uid_t uid2 = ((setres_count + 1) % 5); 351 uid_t uid3 = ((setres_count + 2) % 5); 352 353 if (setresuid(uid1, uid2, uid3) < 0) { 354 if (errno != EPERM) { 355 close(pipefd[1]); 356 exit(1); 357 } 358 } 359 } 360 361 close(pipefd[1]); 362 exit(0); 363 } 364 365 /* Parent process */ 366 close(pipefd[1]); 367 368 if (read(pipefd[0], &userns_id, sizeof(userns_id)) != sizeof(userns_id)) { 369 close(pipefd[0]); 370 kill(pid, SIGKILL); 371 waitpid(pid, NULL, 0); 372 SKIP(return, "Failed to get namespace ID from child"); 373 } 374 close(pipefd[0]); 375 376 waitpid(pid, &status, 0); 377 ASSERT_TRUE(WIFEXITED(status)); 378 ASSERT_EQ(WEXITSTATUS(status), 0); 379 380 /* Verify namespace becomes inactive */ 381 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 382 if (ret < 0) { 383 if (errno == ENOSYS) 384 SKIP(return, "listns() not supported"); 385 ASSERT_GE(ret, 0); 386 } 387 388 for (i = 0; i < ret; i++) { 389 if (ns_ids[i] == userns_id) { 390 found = true; 391 break; 392 } 393 } 394 395 ASSERT_FALSE(found); 396 TH_LOG("setresuid() correctly preserved active references (no leak)"); 397 } 398 399 /* 400 * Test credential changes across multiple user namespaces. 401 * Create nested user namespaces and verify active reference tracking. 402 */ 403 TEST(cred_change_nested_userns) 404 { 405 pid_t pid; 406 int status; 407 __u64 parent_userns_id, child_userns_id; 408 struct ns_id_req req = { 409 .size = sizeof(req), 410 .spare = 0, 411 .ns_id = 0, 412 .ns_type = CLONE_NEWUSER, 413 .spare2 = 0, 414 .user_ns_id = 0, 415 }; 416 __u64 ns_ids[256]; 417 ssize_t ret; 418 int i; 419 bool found_parent = false, found_child = false; 420 int pipefd[2]; 421 422 ASSERT_EQ(pipe(pipefd), 0); 423 424 pid = fork(); 425 ASSERT_GE(pid, 0); 426 427 if (pid == 0) { 428 /* Child process */ 429 int fd, userns_fd; 430 __u64 parent_id, child_id; 431 uid_t orig_uid = getuid(); 432 433 close(pipefd[0]); 434 435 /* Create first user namespace */ 436 userns_fd = get_userns_fd(0, orig_uid, 1); 437 if (userns_fd < 0) { 438 close(pipefd[1]); 439 exit(1); 440 } 441 442 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 443 close(userns_fd); 444 close(pipefd[1]); 445 exit(1); 446 } 447 close(userns_fd); 448 449 /* Get first namespace ID */ 450 fd = open("/proc/self/ns/user", O_RDONLY); 451 if (fd < 0) { 452 close(pipefd[1]); 453 exit(1); 454 } 455 456 if (ioctl(fd, NS_GET_ID, &parent_id) < 0) { 457 close(fd); 458 close(pipefd[1]); 459 exit(1); 460 } 461 close(fd); 462 463 /* Create nested user namespace */ 464 userns_fd = get_userns_fd(0, 0, 1); 465 if (userns_fd < 0) { 466 close(pipefd[1]); 467 exit(1); 468 } 469 470 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 471 close(userns_fd); 472 close(pipefd[1]); 473 exit(1); 474 } 475 close(userns_fd); 476 477 /* Get nested namespace ID */ 478 fd = open("/proc/self/ns/user", O_RDONLY); 479 if (fd < 0) { 480 close(pipefd[1]); 481 exit(1); 482 } 483 484 if (ioctl(fd, NS_GET_ID, &child_id) < 0) { 485 close(fd); 486 close(pipefd[1]); 487 exit(1); 488 } 489 close(fd); 490 491 /* Send both IDs to parent */ 492 write(pipefd[1], &parent_id, sizeof(parent_id)); 493 write(pipefd[1], &child_id, sizeof(child_id)); 494 495 /* Perform some credential changes in nested namespace */ 496 setuid(0); 497 setgid(0); 498 499 close(pipefd[1]); 500 exit(0); 501 } 502 503 /* Parent process */ 504 close(pipefd[1]); 505 506 /* Read both namespace IDs */ 507 if (read(pipefd[0], &parent_userns_id, sizeof(parent_userns_id)) != sizeof(parent_userns_id)) { 508 close(pipefd[0]); 509 kill(pid, SIGKILL); 510 waitpid(pid, NULL, 0); 511 SKIP(return, "Failed to get parent namespace ID"); 512 } 513 514 if (read(pipefd[0], &child_userns_id, sizeof(child_userns_id)) != sizeof(child_userns_id)) { 515 close(pipefd[0]); 516 kill(pid, SIGKILL); 517 waitpid(pid, NULL, 0); 518 SKIP(return, "Failed to get child namespace ID"); 519 } 520 close(pipefd[0]); 521 522 TH_LOG("Parent userns: %llu, Child userns: %llu", 523 (unsigned long long)parent_userns_id, 524 (unsigned long long)child_userns_id); 525 526 /* Verify both namespaces are active */ 527 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 528 if (ret < 0) { 529 kill(pid, SIGKILL); 530 waitpid(pid, NULL, 0); 531 if (errno == ENOSYS) 532 SKIP(return, "listns() not supported"); 533 ASSERT_GE(ret, 0); 534 } 535 536 for (i = 0; i < ret; i++) { 537 if (ns_ids[i] == parent_userns_id) 538 found_parent = true; 539 if (ns_ids[i] == child_userns_id) 540 found_child = true; 541 } 542 543 ASSERT_TRUE(found_parent); 544 ASSERT_TRUE(found_child); 545 546 /* Wait for child */ 547 waitpid(pid, &status, 0); 548 ASSERT_TRUE(WIFEXITED(status)); 549 ASSERT_EQ(WEXITSTATUS(status), 0); 550 551 /* Verify both namespaces become inactive */ 552 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 553 ASSERT_GE(ret, 0); 554 555 found_parent = false; 556 found_child = false; 557 for (i = 0; i < ret; i++) { 558 if (ns_ids[i] == parent_userns_id) 559 found_parent = true; 560 if (ns_ids[i] == child_userns_id) 561 found_child = true; 562 } 563 564 ASSERT_FALSE(found_parent); 565 ASSERT_FALSE(found_child); 566 TH_LOG("Nested user namespace credential changes preserved active refs (no leak)"); 567 } 568 569 /* 570 * Test rapid credential changes don't cause refcount imbalances. 571 * This stress-tests the switch_cred_namespaces() logic. 572 */ 573 TEST(rapid_cred_changes_no_leak) 574 { 575 pid_t pid; 576 int status; 577 __u64 userns_id; 578 struct ns_id_req req = { 579 .size = sizeof(req), 580 .spare = 0, 581 .ns_id = 0, 582 .ns_type = CLONE_NEWUSER, 583 .spare2 = 0, 584 .user_ns_id = 0, 585 }; 586 __u64 ns_ids[256]; 587 ssize_t ret; 588 int i; 589 bool found = false; 590 int pipefd[2]; 591 592 ASSERT_EQ(pipe(pipefd), 0); 593 594 pid = fork(); 595 ASSERT_GE(pid, 0); 596 597 if (pid == 0) { 598 /* Child process */ 599 int fd, userns_fd; 600 __u64 child_userns_id; 601 uid_t orig_uid = getuid(); 602 int change_count; 603 604 close(pipefd[0]); 605 606 /* Create new user namespace with wider range of UIDs/GIDs */ 607 userns_fd = get_userns_fd(0, orig_uid, 100); 608 if (userns_fd < 0) { 609 close(pipefd[1]); 610 exit(1); 611 } 612 613 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 614 close(userns_fd); 615 close(pipefd[1]); 616 exit(1); 617 } 618 close(userns_fd); 619 620 /* Get user namespace ID */ 621 fd = open("/proc/self/ns/user", O_RDONLY); 622 if (fd < 0) { 623 close(pipefd[1]); 624 exit(1); 625 } 626 627 if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) { 628 close(fd); 629 close(pipefd[1]); 630 exit(1); 631 } 632 close(fd); 633 634 write(pipefd[1], &child_userns_id, sizeof(child_userns_id)); 635 636 /* 637 * Perform many rapid credential changes. 638 * Mix setuid, setgid, setreuid, setregid, setresuid, setresgid. 639 */ 640 for (change_count = 0; change_count < 200; change_count++) { 641 switch (change_count % 6) { 642 case 0: 643 setuid(change_count % 50); 644 break; 645 case 1: 646 setgid(change_count % 50); 647 break; 648 case 2: 649 setreuid(change_count % 50, (change_count + 1) % 50); 650 break; 651 case 3: 652 setregid(change_count % 50, (change_count + 1) % 50); 653 break; 654 case 4: 655 setresuid(change_count % 50, (change_count + 1) % 50, (change_count + 2) % 50); 656 break; 657 case 5: 658 setresgid(change_count % 50, (change_count + 1) % 50, (change_count + 2) % 50); 659 break; 660 } 661 } 662 663 close(pipefd[1]); 664 exit(0); 665 } 666 667 /* Parent process */ 668 close(pipefd[1]); 669 670 if (read(pipefd[0], &userns_id, sizeof(userns_id)) != sizeof(userns_id)) { 671 close(pipefd[0]); 672 kill(pid, SIGKILL); 673 waitpid(pid, NULL, 0); 674 SKIP(return, "Failed to get namespace ID from child"); 675 } 676 close(pipefd[0]); 677 678 TH_LOG("Testing with user namespace ID: %llu", (unsigned long long)userns_id); 679 680 waitpid(pid, &status, 0); 681 ASSERT_TRUE(WIFEXITED(status)); 682 ASSERT_EQ(WEXITSTATUS(status), 0); 683 684 /* Verify namespace becomes inactive (no leaked active refs) */ 685 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 686 if (ret < 0) { 687 if (errno == ENOSYS) 688 SKIP(return, "listns() not supported"); 689 ASSERT_GE(ret, 0); 690 } 691 692 for (i = 0; i < ret; i++) { 693 if (ns_ids[i] == userns_id) { 694 found = true; 695 break; 696 } 697 } 698 699 ASSERT_FALSE(found); 700 TH_LOG("200 rapid credential changes completed with no active ref leak"); 701 } 702 703 /* 704 * Test setfsuid/setfsgid which change filesystem UID/GID. 705 * These also trigger credential changes but may have different code paths. 706 */ 707 TEST(setfsuid_preserves_active_refs) 708 { 709 pid_t pid; 710 int status; 711 __u64 userns_id; 712 struct ns_id_req req = { 713 .size = sizeof(req), 714 .spare = 0, 715 .ns_id = 0, 716 .ns_type = CLONE_NEWUSER, 717 .spare2 = 0, 718 .user_ns_id = 0, 719 }; 720 __u64 ns_ids[256]; 721 ssize_t ret; 722 int i; 723 bool found = false; 724 int pipefd[2]; 725 726 ASSERT_EQ(pipe(pipefd), 0); 727 728 pid = fork(); 729 ASSERT_GE(pid, 0); 730 731 if (pid == 0) { 732 /* Child process */ 733 int fd, userns_fd; 734 __u64 child_userns_id; 735 uid_t orig_uid = getuid(); 736 int change_count; 737 738 close(pipefd[0]); 739 740 /* Create new user namespace */ 741 userns_fd = get_userns_fd(0, orig_uid, 10); 742 if (userns_fd < 0) { 743 close(pipefd[1]); 744 exit(1); 745 } 746 747 if (setns(userns_fd, CLONE_NEWUSER) < 0) { 748 close(userns_fd); 749 close(pipefd[1]); 750 exit(1); 751 } 752 close(userns_fd); 753 754 /* Get user namespace ID */ 755 fd = open("/proc/self/ns/user", O_RDONLY); 756 if (fd < 0) { 757 close(pipefd[1]); 758 exit(1); 759 } 760 761 if (ioctl(fd, NS_GET_ID, &child_userns_id) < 0) { 762 close(fd); 763 close(pipefd[1]); 764 exit(1); 765 } 766 close(fd); 767 768 write(pipefd[1], &child_userns_id, sizeof(child_userns_id)); 769 770 /* Perform multiple setfsuid/setfsgid calls */ 771 for (change_count = 0; change_count < 50; change_count++) { 772 setfsuid(change_count % 10); 773 setfsgid(change_count % 10); 774 } 775 776 close(pipefd[1]); 777 exit(0); 778 } 779 780 /* Parent process */ 781 close(pipefd[1]); 782 783 if (read(pipefd[0], &userns_id, sizeof(userns_id)) != sizeof(userns_id)) { 784 close(pipefd[0]); 785 kill(pid, SIGKILL); 786 waitpid(pid, NULL, 0); 787 SKIP(return, "Failed to get namespace ID from child"); 788 } 789 close(pipefd[0]); 790 791 waitpid(pid, &status, 0); 792 ASSERT_TRUE(WIFEXITED(status)); 793 ASSERT_EQ(WEXITSTATUS(status), 0); 794 795 /* Verify namespace becomes inactive */ 796 ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0); 797 if (ret < 0) { 798 if (errno == ENOSYS) 799 SKIP(return, "listns() not supported"); 800 ASSERT_GE(ret, 0); 801 } 802 803 for (i = 0; i < ret; i++) { 804 if (ns_ids[i] == userns_id) { 805 found = true; 806 break; 807 } 808 } 809 810 ASSERT_FALSE(found); 811 TH_LOG("setfsuid/setfsgid correctly preserved active references (no leak)"); 812 } 813 814 TEST_HARNESS_MAIN 815