Closed joanbm closed 4 years ago
For easier reproduction without messing too much with your system you can use this Dockerfile (run with xhost local:root && sudo docker build . -t i3lock_docker && sudo docker run -it --rm --env="DISPLAY" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" i3lock_docker
):
FROM alpine:edge
# Install build tools + i3lock depends and makedepends
RUN apk add --update \
make gcc musl-dev valgrind \
xkeyboard-config \
libev-dev cairo-dev linux-pam-dev libxkbcommon-dev \
xcb-util-image-dev xcb-util-xrm-dev \
&& rm -rf /var/cache/apk/*
# Download + build i3lock (with easy race condition trigger patch)
RUN wget https://i3wm.org/i3lock/i3lock-2.12.tar.bz2
RUN echo '84f1558368381bcad9a64f41ab6134a6614dea453d1ee5ecfe886185b9e1baebeeca446c4635158deb8dae5b25c09d47d3990239d76c44e5325ca5bfaad9b2ad i3lock-2.12.tar.bz2' |sha512sum -c -
RUN tar xf i3lock-2.12.tar.bz2
WORKDIR i3lock-2.12
RUN sed -i -e 's/ev_invoke/sleep(3); ev_invoke/g' i3lock.c
RUN ./configure && make -j
# Attempt to reproduce the problem
RUN echo "root:1" | chpasswd
RUN echo -e '#!/bin/sh \n\
echo i3lock will launch shortly. To reproduce the problem, \n\
echo alternately press 1 and ENTER after the launcher starts \n\
sleep 5 \n\
valgrind x86_64-pc-linux-musl/i3lock &disown \n\
sleep 5 \n\
pkill -9 valgrind' > launchscript.sh
ENTRYPOINT sh launchscript.sh
Thanks for this incredibly well-researched report. I’ll try reproducing and fixing it once I get a chance
Now fixed in master. Let me know if there’s anything left to fix, and thanks again for the great report :)
@stapelberg Thanks and my pleasure 😉! Fix is working correctly on my side.
I'm submitting a…
[X ] Bug [ ] Feature Request [ ] Other (Please describe in detail)
Current Behavior
I found that, if one successfully authenticates (i.e. types the correct password and then presses ENTER), but then immediately attempts a further authentication (i.e. types a correct or incorrect password and then presses ENTER), it's possible that i3lock hangs (in my systems, the behaviour is that i3lock stays locked and it's impossible to unlock it even when using the correct password). Analysis with
valgrind
shows that invalid memory accesses cause this behaviour, so other effects like a crash may also be possible.In the previous paragraph, immediately means "before the previous authentication event was completely processed by i3lock". So, this is a race condition that usually happens under a very precise, sub-second timeframe. Later, I provide a way to easily reproduce this race condition and a more detailed analysis.
Expected Behavior
Since there was already a successful authentication event, the locker should gracefully shut down and the later authentication events should be ignored.
Reproduction Instructions
Since this is a race condition, it may be difficult to reproduce this problem, let alone consistently. However, I found a way to reproduce this problem with 100% consistency.
For reproducing the problem, I recommend changing your password to something short (but not empty). For example, change your password to just a single "1" character.
Next, introduce a
sleep
statement in the i3lock.cmain
function, just before starting the event loop, and recompile the code:To reproduce the problem, launch the just-compiled
i3lock
binary and start alternately spamming your password and ENTER, e.g. alternately press "1" and ENTER. You should be able to shortly reproduce the problem. Since it may not have the same behavior as in my system, I recommend also attachingvalgrind
so you can at least observe the invalid memory accesses.(Make sure you have access to an alternate TTY so you can kill i3lock in case the behavior is a hang)
Analysis of the
valgrind
output shows many invalid memory access like the following:Maybe this is also related to issue #252 (some of the invalid accesses are reported in
pam_set_item
like in the Fedora crash report). Full log on Arch Linux, with PAM debug symbols valgrind_log.txt.zip(If you're wondering how I originally ran into this problem, given that it's caused by such a precise race condition, it's because I was using a patched version of i3lock that allows me to authenticate with a USB key instead of with a password. So, to log in, I was just spamming/holding ENTER after inserting the USB key. This was enough for me to reproduce the problem without any synthetic
sleep
statement).Technical explanation
The problem is the way the
input_done
function is implemented:On a successful authentication event,
pam_authenticate
returnsPAM_SUCCESS
, and thenpam_end
is called, releasing the resources associated with the PAM handle. Then, it callsev_break
to exit the main loop and thus exit the locker application and unlock the screen.However, calling
ev_break
does not immediately exit the main loop, but it only does this after all pending events have been processed. Fromman ev
:Therefore, by quickly causing another authentication event after a successful authentication event, it's possible to reenter
input_done
after the PAM handle has been released. This causes the call topam_authenticate
to use an already-released PAM handle which causes the invalid memory reads and incorrect behavior reported.Running a
git blame
shows that i3lock formerly used to callexit(0)
immediately afterpam_end
, so this wasn't a problem before. However, this was changed in commit 5b4d45a8aff1dacaad364ae6df70c3a6a5067701.To fix the problem, it should be enough to move the call to
pam_end
to the cleanup code inmain
after the main loop exits, instead of immediately releasing it afterpam_authenticate
(I am not sure if the call topam_setcred
should also be moved or not, since I'm not familiar with the problem it's trying to solve).Environment
Output of
i3lock --version
:Problem reproduced 2020-01-02 on Arch Linux, Alpine Linux Edge, on both i3lock 2.12 and git master