]>
Commit | Line | Data |
---|---|---|
b0a679f4 | 1 | /* Test cancellation of getpwuid_r. |
6d7e8eda | 2 | Copyright (C) 2016-2023 Free Software Foundation, Inc. |
b0a679f4 CD |
3 | This file is part of the GNU C Library. |
4 | ||
5 | The GNU C Library is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU Lesser General Public | |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
9 | ||
10 | The GNU C Library 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 the GNU | |
13 | Lesser General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU Lesser General Public | |
16 | License along with the GNU C Library; if not, see | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
b0a679f4 CD |
18 | |
19 | /* Test if cancellation of getpwuid_r incorrectly leaves internal | |
20 | function state locked resulting in hang of subsequent calls to | |
21 | getpwuid_r. The main thread creates a second thread which will do | |
22 | the calls to getpwuid_r. A semaphore is used by the second thread to | |
23 | signal to the main thread that it is as close as it can be to the | |
24 | call site of getpwuid_r. The goal of the semaphore is to avoid any | |
25 | cancellable function calls between the sem_post and the call to | |
26 | getpwuid_r. The main thread then attempts to cancel the second | |
27 | thread. Without the fixes the cancellation happens at any number of | |
28 | calls to cancellable functions in getpuid_r, but with the fix the | |
29 | cancellation either does not happen or happens only at expected | |
30 | points where the internal state is consistent. We use an explicit | |
31 | pthread_testcancel call to terminate the loop in a timely fashion | |
32 | if the implementation does not have a cancellation point. */ | |
33 | ||
34 | #include <stdio.h> | |
35 | #include <stdlib.h> | |
36 | #include <pthread.h> | |
37 | #include <pwd.h> | |
5e4e1063 | 38 | #include <nss.h> |
b0a679f4 CD |
39 | #include <sys/types.h> |
40 | #include <unistd.h> | |
41 | #include <semaphore.h> | |
42 | #include <errno.h> | |
43 | #include <support/support.h> | |
44 | ||
45 | sem_t started; | |
46 | char *wbuf; | |
47 | long wbufsz; | |
48 | ||
49 | void | |
50 | worker_free (void *arg) | |
51 | { | |
52 | free (arg); | |
53 | } | |
54 | ||
55 | static void * | |
56 | worker (void *arg) | |
57 | { | |
58 | int ret; | |
59 | unsigned int iter = 0; | |
60 | struct passwd pwbuf, *pw; | |
61 | uid_t uid; | |
62 | ||
63 | uid = geteuid (); | |
64 | ||
65 | /* Use a reasonable sized buffer. Note that _SC_GETPW_R_SIZE_MAX is | |
66 | just a hint and not any kind of maximum value. */ | |
67 | wbufsz = sysconf (_SC_GETPW_R_SIZE_MAX); | |
68 | if (wbufsz == -1) | |
69 | wbufsz = 1024; | |
70 | wbuf = xmalloc (wbufsz); | |
71 | ||
72 | pthread_cleanup_push (worker_free, wbuf); | |
73 | sem_post (&started); | |
74 | while (1) | |
75 | { | |
76 | iter++; | |
77 | ||
78 | ret = getpwuid_r (uid, &pwbuf, wbuf, wbufsz, &pw); | |
79 | ||
80 | /* The call to getpwuid_r may not cancel so we need to test | |
81 | for cancellation after some number of iterations of the | |
82 | function. Choose an arbitrary 100,000 iterations of running | |
83 | getpwuid_r in a tight cancellation loop before testing for | |
84 | cancellation. */ | |
85 | if (iter > 100000) | |
86 | pthread_testcancel (); | |
87 | ||
88 | if (ret == ERANGE) | |
89 | { | |
90 | /* Increase the buffer size. */ | |
91 | free (wbuf); | |
92 | wbufsz = wbufsz * 2; | |
93 | wbuf = xmalloc (wbufsz); | |
94 | } | |
95 | ||
96 | } | |
97 | pthread_cleanup_pop (1); | |
98 | ||
99 | return NULL; | |
100 | } | |
101 | ||
102 | static int | |
103 | do_test (void) | |
104 | { | |
105 | int ret; | |
106 | char *buf; | |
107 | long bufsz; | |
108 | void *retval; | |
109 | struct passwd pwbuf, *pw; | |
110 | pthread_t thread; | |
111 | ||
112 | /* Configure the test to only use files. We control the files plugin | |
113 | as part of glibc so we assert that it should be deferred | |
114 | cancellation safe. */ | |
115 | __nss_configure_lookup ("passwd", "files"); | |
116 | ||
117 | /* Use a reasonable sized buffer. Note that _SC_GETPW_R_SIZE_MAX is | |
118 | just a hint and not any kind of maximum value. */ | |
119 | bufsz = sysconf (_SC_GETPW_R_SIZE_MAX); | |
120 | if (bufsz == -1) | |
121 | bufsz = 1024; | |
122 | buf = xmalloc (bufsz); | |
123 | ||
124 | sem_init (&started, 0, 0); | |
125 | ||
126 | pthread_create (&thread, NULL, worker, NULL); | |
127 | ||
128 | do | |
129 | { | |
130 | ret = sem_wait (&started); | |
131 | if (ret == -1 && errno != EINTR) | |
132 | { | |
133 | printf ("FAIL: Failed to wait for second thread to start.\n"); | |
134 | exit (EXIT_FAILURE); | |
135 | } | |
136 | } | |
137 | while (ret != 0); | |
138 | ||
139 | printf ("INFO: Cancelling thread\n"); | |
140 | if ((ret = pthread_cancel (thread)) != 0) | |
141 | { | |
142 | printf ("FAIL: Failed to cancel thread. Returned %d\n", ret); | |
143 | exit (EXIT_FAILURE); | |
144 | } | |
145 | ||
146 | printf ("INFO: Joining...\n"); | |
147 | pthread_join (thread, &retval); | |
148 | if (retval != PTHREAD_CANCELED) | |
149 | { | |
150 | printf ("FAIL: Thread was not cancelled.\n"); | |
151 | exit (EXIT_FAILURE); | |
152 | } | |
153 | printf ("INFO: Joined, trying getpwuid_r call\n"); | |
154 | ||
155 | /* Before the fix in 312be3f9f5eab1643d7dcc7728c76d413d4f2640 for this | |
156 | issue the cancellation point could happen in any number of internal | |
157 | calls, and therefore locks would be left held and the following | |
158 | call to getpwuid_r would block and the test would time out. */ | |
159 | do | |
160 | { | |
161 | ret = getpwuid_r (geteuid (), &pwbuf, buf, bufsz, &pw); | |
162 | if (ret == ERANGE) | |
163 | { | |
164 | /* Increase the buffer size. */ | |
165 | free (buf); | |
166 | bufsz = bufsz * 2; | |
167 | buf = xmalloc (bufsz); | |
168 | } | |
169 | } | |
170 | while (ret == ERANGE); | |
171 | ||
172 | free (buf); | |
173 | ||
174 | /* Before the fix we would never get here. */ | |
175 | printf ("PASS: Canceled getpwuid_r successfully" | |
176 | " and called it again without blocking.\n"); | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
983a9637 | 181 | #define TIMEOUT 900 |
b0a679f4 | 182 | #include <support/test-driver.c> |