File Index
Files and directories
Sign in
FILE

cred_change_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/namespaces/cred_change_test.c C
  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 */
 31TEST(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 */
168TEST(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 */
284TEST(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 */
403TEST(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 */
573TEST(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 */
707TEST(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
814TEST_HARNESS_MAIN
~/ Read-only file preview.