1 | /* Test cancellation of getpwuid_r. |
2 | Copyright (C) 2016-2022 Free Software Foundation, Inc. |
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 |
17 | <https://www.gnu.org/licenses/>. */ |
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> |
38 | #include <nss.h> |
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 (ptr: 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 (n: wbufsz); |
71 | |
72 | pthread_cleanup_push (worker_free, wbuf); |
73 | sem_post (sem: &started); |
74 | while (1) |
75 | { |
76 | iter++; |
77 | |
78 | ret = getpwuid_r (uid: uid, resultbuf: &pwbuf, buffer: wbuf, buflen: wbufsz, result: &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 (ptr: wbuf); |
92 | wbufsz = wbufsz * 2; |
93 | wbuf = xmalloc (n: 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 (dbname: "passwd" , string: "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 (n: bufsz); |
123 | |
124 | sem_init (sem: &started, pshared: 0, value: 0); |
125 | |
126 | pthread_create (newthread: &thread, NULL, start_routine: worker, NULL); |
127 | |
128 | do |
129 | { |
130 | ret = sem_wait (sem: &started); |
131 | if (ret == -1 && errno != EINTR) |
132 | { |
133 | printf (format: "FAIL: Failed to wait for second thread to start.\n" ); |
134 | exit (EXIT_FAILURE); |
135 | } |
136 | } |
137 | while (ret != 0); |
138 | |
139 | printf (format: "INFO: Cancelling thread\n" ); |
140 | if ((ret = pthread_cancel (th: thread)) != 0) |
141 | { |
142 | printf (format: "FAIL: Failed to cancel thread. Returned %d\n" , ret); |
143 | exit (EXIT_FAILURE); |
144 | } |
145 | |
146 | printf (format: "INFO: Joining...\n" ); |
147 | pthread_join (th: thread, thread_return: &retval); |
148 | if (retval != PTHREAD_CANCELED) |
149 | { |
150 | printf (format: "FAIL: Thread was not cancelled.\n" ); |
151 | exit (EXIT_FAILURE); |
152 | } |
153 | printf (format: "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 (uid: geteuid (), resultbuf: &pwbuf, buffer: buf, buflen: bufsz, result: &pw); |
162 | if (ret == ERANGE) |
163 | { |
164 | /* Increase the buffer size. */ |
165 | free (ptr: buf); |
166 | bufsz = bufsz * 2; |
167 | buf = xmalloc (n: bufsz); |
168 | } |
169 | } |
170 | while (ret == ERANGE); |
171 | |
172 | free (ptr: buf); |
173 | |
174 | /* Before the fix we would never get here. */ |
175 | printf (format: "PASS: Canceled getpwuid_r successfully" |
176 | " and called it again without blocking.\n" ); |
177 | |
178 | return 0; |
179 | } |
180 | |
181 | #define TIMEOUT 900 |
182 | #include <support/test-driver.c> |
183 | |