Don't allow an unrelated client to unlock the session - #2127
Conversation
8ae809a to
c67a6e6
Compare
|
I realized there's also an issue (perhaps exacerbated by the first commit in this PR) where the |
85d88d6 to
506b854
Compare
It feels like we should mark the object as "done" on We need to make sure, that we don't break a new lock client taking over after an old one died. So this still needs to work:
|
ad3157b to
d3691cf
Compare
|
Ok, here's another pass at this. For starters, I simplified a few things in my original implementation (mainly, Added the "done" flag. It's mainly just used now to decide whether or not to call functions on A judgment call I made: the Regarding allowing a new locker to take over if an old locker crashes... I looked through the old code, and I think that was only supported by accident. There was never anything that would ever set The new code did end up breaking this, so I implemented Do we need a |
I think that is fine.
Setting it to "Unlocked" is kind of a misnomer, if the session very much is still locked, even if this does the correct thing. I wonder if we should introduce a
Yeah, I wouldn't add that, if nobody is asking for it.
I think it is, we end up in the defunct state since the protocol explicitly forbids it. If the client is misbehaving either by crashing or handling the protocol wrong the compositor cannot know the intent and has to prioritize the users privacy. |
Here's the "attack" sequence that was possible: 1. Legit lock client binds to interface, sends `lock`, creates lock surfaces, and does things as expected. Session is locked. 2. Malicious (or just buggy) client binds to the interface and sends `lock`. Smithay calls `SessionLockHandler::lock()`. Here the compositor does have enough information; it can know that the session is already locked, and that it's a different client requesting to lock, so it can drop the `SessionLocker` instance, which causes smithay to emit `finished` on the protocol object. 3. But `finished` is not a destructor, so the client still has a live `ext_session_lock_v1` object. It can immediately request `unlock_and_destroy` on it. 4. Smithay's handler for `unlock_and_destroy` unconditionally clears the list of lock surfaces, and calls `SessionLockHandler::unlock()`. `unlock()` takes no parameters, so the compositor has no idea what client is asking to unlock the session. Presumably it will unlock the session, without the user having authenticated. One possible mitigation the compositor could do (which doesn't require changes to smithay) would be to immediately send a protocol error to the client after step Smithay#2, which would disconnect the client entirely. I don't think that's a very nice solution, though, as it's possible the client isn't malicious or even buggy, but just raced the client that successfully locked the screen. Regardless, this would be a huge foot-gun for compositors; it's not obvious or clear that the compositor should have to do this, and arguably smithay should just do the thing that is correct in all circumstances. So, this change: 1. Moves `lock_status` to `SessionLockManagerState`, since whether or not the session is locked is a property of the compositor/manager, not of a specific `ExtSessionLockV1` instance. 2. `lock_status` is now an enum that records the `ExtSessionLockV1` instance that the compositor allowed to successfully lock the session. 3. If another `ExtSessionLockV1` instance tries to unlock the session, an error is sent back (`invalid_unlock`), the internal list of lock surfaces is not cleared, and `unlock()` is not called on the compositor's handler. 4. Moves `locked_outputs` from `SessionLockManagerState` to `SessionLockState`. Since there can be more than one `ExtSessionLockV1` instance alive at the same time, and the compositor may not have sent `locked` or `finished` to any of them yet, they are all allowed to create lock surfaces, so they should only be sent a `duplicate_output` error if they try to create more then one lock surface per output *per instance*. 5. This also stores the `ExtSessionLockV1` instance on the `LockSurface`, and adds an accessor, because the compositor needs to know which lock surface is associated with which lock instance, so it can only keep and show the ones associated with the lock instance that ends up actually locking the session.
d3691cf to
c2828cf
Compare
Yes, in my compositor I use Ok, updated! |
This is set to true once `finished` is emitted on the `ExtSessionLockV1` object, and then prevents requests on that object (as well as any requests on any `ExtSessionLockSurfaceV1` instances associated with it) from triggering any calls on `SessionLockHandler`.
Per the spec, the compositor should not unlock the session if the client crashes or disconnects without explicitly sending `unlock_and_destroy` on the lock instance. However, it is up to the compositor if it wants to treat this situation as "locked forever", or if it will allow the locker to restart and re-lock the session. The latter will not work if smithay's internal state stays as `Locked`, so we introduce a new state, `Defunct`, to represent this situation. Smithay doesn't treat `Unlocked` or `Defunct` differently right now, as it doesn't need to (that is up to compositor policy).
c2828cf to
2955aff
Compare
Description
Here's the "attack" sequence that was possible:
lock, creates lock surfaces, and does things as expected. Session is locked.lock. Smithay callsSessionLockHandler::lock(). Here the compositor does have enough information; it can know that the session is already locked, and that it's a different client requesting to lock, so it can drop theSessionLockerinstance, which causes smithay to emitfinishedon the protocol object.finishedis not a destructor, so the client still has a liveext_session_lock_v1object. It can immediately requestunlock_and_destroyon it.unlock_and_destroyunconditionally clears the list of lock surfaces, and callsSessionLockHandler::unlock().unlock()takes no parameters, so the compositor has no idea what client is asking to unlock the session. Presumably it will unlock the session, without the user having authenticated.One possible mitigation the compositor could do (which doesn't require changes to smithay) would be to immediately send a protocol error to the client after step #2, which would disconnect the client entirely. I don't think that's a very nice solution, though, as it's possible the client isn't malicious or even buggy, but just raced the client that successfully locked the screen. Regardless, this would be a huge foot-gun for compositors; it's not obvious or clear that the compositor should have to do this, and arguably smithay should just do the thing that is correct in all circumstances.
So, this change:
lock_statustoSessionLockManagerState, since whether or not the session is locked is a property of the compositor/manager, not of a specificExtSessionLockV1instance.lock_statusis now an enum that records theExtSessionLockV1instance that the compositor allowed to successfully lock the session.ExtSessionLockV1instance tries to unlock the session, an error is sent back (invalid_unlock), the internal list of lock surfaces is not cleared, andunlock()is not called on the compositor's handler.Checklist