Bug 40348

Summary: due to lock race, concurrent conflicting transactions can complete
Product: Sisyphus Reporter: Ivan Zakharyaschev <imz>
Component: rpmAssignee: placeholder <placeholder>
Status: NEW --- QA Contact: qa-sisyphus
Severity: normal    
Priority: P5 CC: at, glebfm, imz, ldv, placeholder, vt
Version: unstable   
Hardware: x86_64   
OS: Linux   
Bug Depends on:    
Bug Blocks: 42504    

Description Ivan Zakharyaschev 2021-07-01 18:51:06 MSK
It's expected that if a transaction started and then succeeded, then the resulting state is consistent (in a certain sense; if it wouldn't be consistent with what the user expects, rpm simply wouldn't start the transaction and report errors in the beginning).

However, due to lock race, concurrent conflicting transactions can complete and result in an unexpected "inconsistent" state (unmet deps; unowned files perhaps).

* * *

rpm -U unpacks the pkg into the filesystem without having a write
lock.

Right after this, another process (like APT) may win the race in
acquiring the write lock and modify the DB differently, in an
incompatible way, say... ...removing another pkg required by the one
being installed by RPM.

This kind of race is acknowledged by rpm developers and is confirmed
by my example.

There can be races when going from read-only access to
the DB (when preparing and doing the deps processing) to write access
(when performing the transaction), and this may be even unsolved on
the level of rpmlib --
https://github.com/rpm-software-management/rpm/blob/992a4f903585cde1ad051b46f89b8b947b1cdae3/lib/rpmts.c#L95

Theoretically, it can't be solved (a guaranteed way to "upgrade" a
read-lock to a write-lock), because then there could be a deadlock when
two process want to "upgrade" the lock at the same time.

A solution could be for rpm to start holding the write lock earlier.
That could mean that scriplets wanting to do rpmquery would block.

So the next part of a solution could be using a pair of locks:

* want write access: acquire exclusively LOCK1, then acquire exclusively LOCK2.
* want read access: acquire non-exclusively LOCK2.
* drop write access: drop LOCK2, then drop LOCK1.
* drop read access: drop LOCK2.
* temporarily give up the write lock: drop LOCK2 (then, perhaps, acquire it non-exclusively).
* restore write lock: acquire exclusively LOCK2 (perhaps, after first dropping it).

So, LOCK1 is a new extra lock, LOCK2 can be treated exactly as the old
lock. The "perhaps" actions in parentheses are optional in the new
protocol, but are possible to look like the old code (if they are
performed, old code and old clients would be in exactly the same
situation as they are now).

Alternatively, what if we identified LOCK1 with the old lock? Then old
clients won't notice us having a read-lock and could start
writing. That's bad.

(I've also found some related discussion at
https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Upgradable_RW_lock
but not found new useful ideas there though.)

How to reproduce:

# rpm -q rpm apt --qf='%{NAME}-%{EVR}:%{DISTTAG}\n'
rpm-4.13.0.1-alt24:p9+254187.340.9.1
apt-0.5.15lorg2-alt71.3:p9+258439.100.1.1

Choose a big pkg (so that you have time to stop rpm -U) that has a
dependency you can easily remove.

SCREEN 1

[root@blacky ~]# rpm -e libmozjs78-debuginfo
[root@blacky ~]# rpm -Uvh /var/cache/apt/archives/libmozjs78-debuginfo_78.0.1-alt3%3asisyphus+274089.1200.2.1@1623242399_x86%5f64.rpm 
Preparing...                                                                                                                                                 #################################################################################################### [100%]
Updating / installing...
1: libmozjs78-debuginfo-78.0.1-alt3                                                                                                                          ############^Z
[1]+  Stopped                 rpm -Uvh /var/cache/apt/archives/libmozjs78-debuginfo_78.0.1-alt3%3asisyphus+274089.1200.2.1@1623242399_x86%5f64.rpm
#

SCREEN 2 (with APT)

[root@blacky ~]# apt-get remove libicu69-debuginfo
Reading Package Lists... Done
Building Dependency Tree... Done
The following packages will be REMOVED:
  libicu69-debuginfo
0 upgraded, 0 newly installed, 1 removed and 1316 not upgraded.
Need to get 0B of archives.
After unpacking 56,3MB disk space will be freed.
Do you want to continue? [Y/n] 
Committing changes...
warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock

SCREEN 2 (alternatively, with RPM)

[root@blacky ~]# rpm -evh libicu69-debuginfo
warning: waiting for transaction lock on /var/lib/rpm/.rpm.lock

SCREEN 1

[root@blacky ~]# fg
rpm -Uvh /var/cache/apt/archives/libmozjs78-debuginfo_78.0.1-alt3%3asisyphus+274089.1200.2.1@1623242399_x86%5f64.rpm
######################################################################################## [100%]
Running /usr/lib/rpm/posttrans-filetriggers
[root@blacky ~]# 

SCREEN 2

Preparing...                                                                                                                                                 #################################################################################################### [100%]
Cleaning up / removing...
1: libicu69-debuginfo-1:6.9.1-alt1                                                                                                                           #################################################################################################### [100%]
Done.
[root@blacky ~]# apt-get check
Reading Package Lists... Done
Building Dependency Tree... Done
You might want to run `install --fix-broken' to correct these.
The following packages have unmet dependencies:
  libmozjs78-debuginfo: Depends: debug64(libicui18n.so.69)
                        Depends: debug64(libicuuc.so.69)
E: Unmet dependencies. Try using --fix-broken.
[root@blacky ~]# 

SCREEN 2 (alternatively, with RPM)

Preparing...                                                                                                                                                 #################################################################################################### [100%]
Cleaning up / removing...
1: libicu69-debuginfo-1:6.9.1-alt1                                                                                                                           #################################################################################################### [100%]
Running /usr/lib/rpm/posttrans-filetriggers
[root@blacky ~]# apt-get check
Reading Package Lists... Done
Building Dependency Tree... Done
You might want to run `install --fix-broken' to correct these.
The following packages have unmet dependencies:
  libmozjs78-debuginfo: Depends: debug64(libicui18n.so.69) but it is not installable
                        Depends: debug64(libicuuc.so.69) but it is not installable
E: Unmet dependencies. Try using --fix-broken.
[root@blacky ~]#