Run authentication in different threads
Patch status: needinfo
Patch by Philippe Virouleau
Long description:
This replaces the commit a305e622, and fixes #895 as well as the bugs introduced by the previous commit (reported in #1090). It uses pthread and lock-free synchronization with gcc builtins atomic operations.
To apply this patch, use:
curl http://cr.i3wm.org/patch/244/raw.patch | git am
b/i3lock.c
16 |
@@ -22,7 +22,6 @@ |
17 |
#include <string.h> |
18 |
#include <ev.h> |
19 |
#include <sys/mman.h> |
20 |
-#include <sys/wait.h> |
21 |
#include <X11/XKBlib.h> |
22 |
#include <X11/extensions/XKBfile.h> |
23 |
#include <xkbcommon/xkbcommon.h> |
24 |
@@ -32,6 +31,7 @@ |
25 |
#include "i3lock.h" |
26 |
#include "xcb.h" |
27 |
#include "cursors.h" |
28 |
+#include "pthread.h" |
29 |
#include "unlock_indicator.h" |
30 |
#include "xinerama.h" |
31 |
|
32 |
@@ -45,13 +45,14 @@ static pam_handle_t *pam_handle; |
33 |
int input_position = 0; |
34 |
/* Holds the password you enter (in UTF-8). */ |
35 |
static char password[512]; |
36 |
+/* Holds the password to be verified (in UTF-8). */ |
37 |
+static char pam_password[512]; |
38 |
static bool beep = false; |
39 |
bool debug_mode = false; |
40 |
static bool dpms = false; |
41 |
bool unlock_indicator = true; |
42 |
static bool dont_fork = false; |
43 |
struct ev_loop *main_loop; |
44 |
-static struct ev_timer *clear_pam_wrong_timeout; |
45 |
extern unlock_state_t unlock_state; |
46 |
extern pam_state_t pam_state; |
47 |
|
48 |
@@ -63,6 +64,9 @@ cairo_surface_t *img = NULL; |
49 |
bool tile = false; |
50 |
bool ignore_empty_password = false; |
51 |
|
52 |
+static int waiting_authentication = 0; |
53 |
+static int max_waiting_authentication = 4; |
54 |
+ |
55 |
/* isutf, u8_dec © 2005 Jeff Bezanson, public domain */ |
56 |
#define isutf(c) (((c) & 0xC0) != 0x80) |
57 |
|
58 |
@@ -157,11 +161,11 @@ out: |
59 |
* cold-boot attacks. |
60 |
* |
61 |
*/ |
62 |
-static void clear_password_memory(void) { |
63 |
+static void clear_password_memory(char *mem) { |
64 |
/* A volatile pointer to the password buffer to prevent the compiler from |
65 |
* optimizing this out. */ |
66 |
- volatile char *vpassword = password; |
67 |
- for (int c = 0; c < sizeof(password); c++) |
68 |
+ volatile char *vpassword = mem; |
69 |
+ for (int c = 0; c < sizeof(mem); c++) |
70 |
/* We store a non-random pattern which consists of the (irrelevant) |
71 |
* index plus (!) the value of the beep variable. This prevents the |
72 |
* compiler from optimizing the calls away, since the value of 'beep' |
73 |
@@ -169,28 +173,28 @@ static void clear_password_memory(void) { |
74 |
vpassword[c] = c + (int)beep; |
75 |
} |
76 |
|
77 |
- |
78 |
/* |
79 |
* Resets pam_state to STATE_PAM_IDLE 2 seconds after an unsuccesful |
80 |
* authentication event. |
81 |
* |
82 |
*/ |
83 |
-static void clear_pam_wrong(EV_P_ ev_timer *w, int revents) { |
84 |
- DEBUG("clearing pam wrong\n"); |
85 |
- pam_state = STATE_PAM_IDLE; |
86 |
- unlock_state = STATE_STARTED; |
87 |
- redraw_screen(); |
88 |
- |
89 |
- /* Now free this timeout. */ |
90 |
- ev_timer_stop(main_loop, clear_pam_wrong_timeout); |
91 |
- free(clear_pam_wrong_timeout); |
92 |
- clear_pam_wrong_timeout = NULL; |
93 |
+static void clear_pam_wrong() { |
94 |
+ /* Clear the pam_wrong sign and go idle if we are in the correct state */ |
95 |
+ if (__sync_bool_compare_and_swap( |
96 |
+ &pam_state, STATE_PAM_WRONG, STATE_PAM_IDLE)) { |
97 |
+ unlock_state = STATE_STARTED; |
98 |
+ redraw_screen(); |
99 |
+ } |
100 |
} |
101 |
|
102 |
-static void clear_input(void) { |
103 |
+static void reset_password(void) { |
104 |
input_position = 0; |
105 |
- clear_password_memory(); |
106 |
+ clear_password_memory(password); |
107 |
password[input_position] = '\0'; |
108 |
+} |
109 |
+ |
110 |
+static void clear_input(void) { |
111 |
+ reset_password(); |
112 |
|
113 |
/* Hide the unlock indicator after a bit if the password buffer is |
114 |
* empty. */ |
115 |
@@ -200,78 +204,106 @@ static void clear_input(void) { |
116 |
unlock_state = STATE_KEY_PRESSED; |
117 |
} |
118 |
|
119 |
-static void auth_failed(void) { |
120 |
- if (debug_mode) |
121 |
- fprintf(stderr, "Authentication failure\n"); |
122 |
+static void *authenticate(void *arg) { |
123 |
+ /* |
124 |
+ * Here we can't just do : |
125 |
+ * while(__sync_bool_compare_and_swap(&pam_state, |
126 |
+ * STATE_PAM_VERIFY, STATE_PAM_VERIFY)) |
127 |
+ * ; |
128 |
+ * |
129 |
+ * And then affect STATE_PAM_VERIFY to pam_state : |
130 |
+ * we need to do both in one single atomic operation, so we use comparison |
131 |
+ * on all the other states. |
132 |
+ * |
133 |
+ */ |
134 |
+ DEBUG("Waiting for critical section\n"); |
135 |
+ __sync_add_and_fetch(&waiting_authentication, 1); |
136 |
+ while (!__sync_bool_compare_and_swap(&pam_state, STATE_PAM_IDLE, STATE_PAM_VERIFY) |
137 |
+ && !__sync_bool_compare_and_swap(&pam_state, STATE_PAM_WRONG, STATE_PAM_VERIFY)) |
138 |
+ ; |
139 |
+ //Beginning of the Critical Section |
140 |
+ DEBUG("Entering critical section\n"); |
141 |
+ __sync_sub_and_fetch(&waiting_authentication, 1); |
142 |
+ |
143 |
+ /* Copy our private password to pam_password and clean */ |
144 |
+ char *the_password = (char *)arg; |
145 |
+ memcpy(pam_password, the_password, sizeof(the_password)); |
146 |
+ clear_password_memory(the_password); |
147 |
+ free(the_password); |
148 |
+ |
149 |
+ |
150 |
|
151 |
- pam_state = STATE_PAM_WRONG; |
152 |
redraw_screen(); |
153 |
|
154 |
- /* Clear this state after 2 seconds (unless the user enters another |
155 |
- * password during that time). */ |
156 |
- ev_now_update(main_loop); |
157 |
- if ((clear_pam_wrong_timeout = calloc(sizeof(struct ev_timer), 1))) { |
158 |
- ev_timer_init(clear_pam_wrong_timeout, clear_pam_wrong, 2.0, 0.); |
159 |
- ev_timer_start(main_loop, clear_pam_wrong_timeout); |
160 |
+ if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) { |
161 |
+ DEBUG("successfully authenticated\n"); |
162 |
+ clear_password_memory(password); |
163 |
+ clear_password_memory(pam_password); |
164 |
+ exit(0); |
165 |
} |
166 |
|
167 |
- /* Cancel the clear_indicator_timeout, it would hide the unlock indicator |
168 |
- * too early. */ |
169 |
- stop_clear_indicator_timeout(); |
170 |
+ if (debug_mode) |
171 |
+ fprintf(stderr, "Authentication failure\n"); |
172 |
+ |
173 |
+ /* Clear the pam_password memory (password has already been cleared) */ |
174 |
+ clear_password_memory(pam_password); |
175 |
|
176 |
/* beep on authentication failure, if enabled */ |
177 |
if (beep) { |
178 |
xcb_bell(conn, 100); |
179 |
xcb_flush(conn); |
180 |
} |
181 |
-} |
182 |
- |
183 |
-static void child_cb(EV_P_ ev_child *child_watcher, int revents) { |
184 |
- if (child_watcher->rstatus != 0) { |
185 |
- DEBUG("Authentication successfull\n"); |
186 |
- clear_password_memory(); |
187 |
|
188 |
- exit(0); |
189 |
- } else { |
190 |
- auth_failed(); |
191 |
+ if (!__sync_bool_compare_and_swap(&pam_state, |
192 |
+ STATE_PAM_VERIFY, STATE_PAM_WRONG)) { |
193 |
+ /* |
194 |
+ * This is an error : once pam_state = STATE_PAM_VERIFY, |
195 |
+ * only one thread can access it, so the comparison should |
196 |
+ * always be true. The atomic operation is here to ensure data |
197 |
+ * coherency with the main thread. |
198 |
+ * Can't exit now as it would unlock the screen, so just return. |
199 |
+ */ |
200 |
+ fprintf(stderr, "i3lock : pam_state is not \"verify\" within\ |
201 |
+ critical section\n"); |
202 |
+ return 0; |
203 |
} |
204 |
- ev_child_stop(main_loop, child_watcher); |
205 |
- free(child_watcher); |
206 |
+ DEBUG("Leaving critical section\n"); |
207 |
+ //End of Critical Section |
208 |
+ redraw_screen(); |
209 |
+ |
210 |
+ |
211 |
+ /* Cancel the clear_indicator_timeout, it would hide the unlock indicator |
212 |
+ * too early. */ |
213 |
+ stop_clear_indicator_timeout(); |
214 |
+ |
215 |
+ /* Wait for 2 seconds and maybe clear the pam_wrong state */ |
216 |
+ sleep(2); |
217 |
+ |
218 |
+ clear_pam_wrong(); |
219 |
+ |
220 |
+ return 0; |
221 |
} |
222 |
|
223 |
static void input_done(void) { |
224 |
- if (pam_state == STATE_PAM_VERIFY) { |
225 |
+ if (waiting_authentication > max_waiting_authentication) |
226 |
return; |
227 |
- } |
228 |
- |
229 |
- if (clear_pam_wrong_timeout) { |
230 |
- ev_timer_stop(main_loop, clear_pam_wrong_timeout); |
231 |
- free(clear_pam_wrong_timeout); |
232 |
- clear_pam_wrong_timeout = NULL; |
233 |
- } |
234 |
+ char *threadprivate_password = malloc(sizeof(password)); |
235 |
+ if (threadprivate_password) { |
236 |
+ memcpy(threadprivate_password, password, sizeof(password)); |
237 |
|
238 |
- pam_state = STATE_PAM_VERIFY; |
239 |
- redraw_screen(); |
240 |
+ /* The thread */ |
241 |
+ pthread_t auth_thread; |
242 |
|
243 |
- /* fork to unblock pam_authenticate */ |
244 |
- pid_t cpid = fork(); |
245 |
- if (cpid == 0) { |
246 |
- exit(pam_authenticate(pam_handle, 0) == PAM_SUCCESS); |
247 |
- } else if (cpid > 0) { |
248 |
- clear_input(); |
249 |
- struct ev_child *child_watcher = calloc(sizeof(struct ev_io), 1); |
250 |
- ev_child_init(child_watcher, child_cb, cpid, 0); |
251 |
- ev_child_set(child_watcher, cpid, 0); |
252 |
- ev_child_start(EV_DEFAULT_ child_watcher); |
253 |
- } else if (cpid < 0) { |
254 |
- DEBUG("Could not fork"); |
255 |
- if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) { |
256 |
- DEBUG("successfully authenticated\n"); |
257 |
- clear_password_memory(); |
258 |
- exit(0); |
259 |
+ /* Create the authentication thread, if it fails, do it in main thread */ |
260 |
+ if(pthread_create(&auth_thread, NULL, authenticate, threadprivate_password)) { |
261 |
+ fprintf(stderr, "Error creating thread, going sequential.\n"); |
262 |
+ authenticate(threadprivate_password); |
263 |
} |
264 |
- auth_failed(); |
265 |
+ } else { |
266 |
+ authenticate(password); |
267 |
} |
268 |
+ |
269 |
+ reset_password(); |
270 |
} |
271 |
|
272 |
/* |
273 |
@@ -472,7 +504,7 @@ static int conv_callback(int num_msg, const struct pam_message **msg, |
274 |
|
275 |
/* return code is currently not used but should be set to zero */ |
276 |
resp[c]->resp_retcode = 0; |
277 |
- if ((resp[c]->resp = strdup(password)) == NULL) { |
278 |
+ if ((resp[c]->resp = strdup(pam_password)) == NULL) { |
279 |
perror("strdup"); |
280 |
return 1; |
281 |
} |