backintime icon indicating copy to clipboard operation
backintime copied to clipboard

PermissionError: [Errno 13] Permission denied: '/tmp/backintime.lock'

Open khaeusler opened this issue 3 months ago • 13 comments

Hello,

I have a problem with the lock file. First the backup of my user is executed and then always the backup of the root account.

The backup of the root user is not executed because the lock file '/tmp/backintime.lock' exists.

If I then start backintime via the console, I get the following message on the console when I click the backup button:

$ sudo backintime-qt_polkit
[sudo] password for XXX:

Back In Time
Version: 1.4.3

Back In Time comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type `backintime --license' for details.

QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'

Back In Time
Version: 1.4.3

Back In Time comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type `backintime --license' for details.

Traceback (most recent call last):
File "/usr/share/backintime/common/backintime.py", line 1190, in <module>
startApp()
File "/usr/share/backintime/common/backintime.py", line 523, in startApp
args.func(args)
File "/usr/share/backintime/common/backintime.py", line 764, in backup
ret = takeSnapshot(cfg, force)
File "/usr/share/backintime/common/backintime.py", line 97, in takeSnapshot
ret = snapshots.Snapshots(cfg).backup(force)
File "/usr/share/backintime/common/snapshots.py", line 732, in backup
self.flockExclusive()  # global flock to block backups from other profiles or users (and run them serialized)
File "/usr/share/backintime/common/snapshots.py", line 2028, in flockExclusive
self.flock = open(self.GLOBAL_FLOCK, 'w')
PermissionError: [Errno 13] Permission denied: '/tmp/backintime.lock'
$ ls -l /tmp/backintime.lock
-rw-rw-rw- 1 XXX XXX 0 Apr  5 12:10 /tmp/backintime.lock

After removing the lock file, the backup runs without errors. The lock file is then created by Backintime as follows:

$ ls -l /tmp/backintime.lock
-rw-rw-rw- 1 root root 0 Apr  5 13:16 /tmp/backintime.lock

However, if the root lock file exists, there are no problems when the normal user starts a backup.

backintime --diagnostics output
$ sudo backintime --diagnostics
[sudo] password for XXX: 
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
QSettings::value: Empty key passed
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-root'
QSettings::value: Empty key passed
{
    "backintime": {
        "name": "Back In Time",
        "version": "1.4.3",
        "latest-config-version": 6,
        "local-config-file": "/root/.config/backintime/config",
        "local-config-file-found": true,
        "global-config-file": "/etc/backintime/config",
        "global-config-file-found": false,
        "started-from": "/usr/share/backintime/common",
        "running-as-root": true,
        "user-callback": "/root/.config/backintime/user-callback",
        "keyring-supported": false
    },
    "host-setup": {
        "platform": "Linux-6.1.0-1036-oem-x86_64-with-glibc2.35",
        "system": "Linux #36-Ubuntu SMP PREEMPT_DYNAMIC Mon Mar 11 17:32:20 UTC 2024",
        "OS": {
            "/etc/os-release": "Ubuntu 22.04.4 LTS",
            "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=22.04\nDISTRIB_CODENAME=jammy\nDISTRIB_DESCRIPTION=\"Ubuntu 22.04.4 LTS\"\n",
            "/etc/debian_version": "bookworm/sid\n"
        },
        "display-system": "($XDG_SESSION_TYPE not set)",
        "locale": "de_DE, UTF-8",
        "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin",
        "RSYNC_OLD_ARGS": "(not set)",
        "RSYNC_PROTECT_ARGS": "(not set)"
    },
    "python-setup": {
        "python": "3.10.12 main Nov 20 2023 15:14:05 CPython GCC 11.4.0",
        "python-executable": "/usr/bin/python3",
        "python-executable-symlink": true,
        "python-executable-resolved": "/usr/bin/python3.10",
        "sys.path": [
            "/usr/share/backintime/qt/plugins",
            "/usr/share/backintime/common/plugins",
            "/usr/share/backintime/plugins",
            "/usr/share/backintime/common",
            "/usr/lib/python310.zip",
            "/usr/lib/python3.10",
            "/usr/lib/python3.10/lib-dynload",
            "/usr/local/lib/python3.10/dist-packages",
            "/usr/lib/python3/dist-packages"
        ],
        "qt": {
            "Version": "PyQt 5.15.6 / Qt 5.15.3",
            "Theme": "breeze",
            "Theme Search Paths": [
                "/usr/share/icons",
                ":/icons"
            ],
            "Fallback Theme": "breeze",
            "Fallback Search Paths": []
        }
    },
    "external-programs": {
        "rsync": {
            "version": "3.2.7",
            "protocol": "31.0",
            "capabilities": {
                "file_bits": 64,
                "inum_bits": 64,
                "timestamp_bits": 64,
                "long_int_bits": 64,
                "socketpairs": true,
                "symlinks": true,
                "symtimes": true,
                "hardlinks": true,
                "hardlink_specials": true,
                "hardlink_symlinks": true,
                "IPv6": true,
                "atimes": true,
                "batchfiles": true,
                "inplace": true,
                "append": true,
                "ACLs": true,
                "xattrs": true,
                "secluded_args": "optional",
                "iconv": true,
                "prealloc": true,
                "stop_at": true,
                "crtimes": false
            },
            "optimizations": {
                "SIMD_roll": true,
                "asm_roll": false,
                "openssl_crypto": true,
                "asm_MD5": false
            },
            "checksum_list": [
                "xxh128",
                "xxh3",
                "xxh64",
                "md5",
                "md4",
                "sha1",
                "none"
            ],
            "compress_list": [
                "zstd",
                "lz4",
                "zlibx",
                "zlib",
                "none"
            ],
            "daemon_auth_list": [
                "sha512",
                "sha256",
                "sha1",
                "md5",
                "md4"
            ]
        },
        "ssh": "OpenSSH_8.9p1 Ubuntu-3ubuntu0.6, OpenSSL 3.0.2 15 Mar 2022",
        "sshfs": "3.7.1",
        "encfs": "1.9.5",
        "shell": "/bin/bash",
        "shell-version": "GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)"
    }
}

khaeusler avatar Apr 05 '24 11:04 khaeusler

Hello Kay, thanks for taking the time to report this.

Questions

Execution

Can you explain in more details how the two backup jobs (the users's and root's one) are "executed"? Manually via GUI, via commandline or schedule via crontab?

For example I wonder about this.

$ sudo backintime-qt_polkit

No need to use sudo here. Just backintime-qt_polkit as regular user. But I am not sure if this will make a difference regarding to your problem.

Global lock settings

Can you have a look (as root & user) in the Manage profiles dialog in the Options tab and see if the option "Run only one snapshot at a time" is set?

Reproduced

It seems I can reproduce the problem (on Debian 12 with BIT 1.4.4-dev).

  1. Setup two snapshots profiles, one as user and one as root.
  2. Enable the "Run only one snapshot at a time" option in Options TAB of the Manage profiles dialog.
  3. As user take a snapshot and close BIT after it. The file /tmp/backintime.lock is created (-rw-rw-rw- 1 user user) and not deleted when BIT is closed.
  4. As root take a snapshot (using --debug switch) and you will get the PermissionError problem.

This not happens when root runs first and user as second snapshot.

To my team mates

[removed because irrelevant]

buhtz avatar Apr 05 '24 11:04 buhtz

Hello Christian,

BIT starts via udev

ACTION=="add|change", ENV{ID_FS_UUID}=="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", RUN+="/usr/bin/su - 'XXX' -c '/usr/bin/nice -n19 /usr/bin/ionice -c2 -n7 /usr/bin/backintime backup-job >/dev/null'"
ACTION=="add|change", ENV{ID_FS_UUID}=="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", RUN+="/usr/bin/su - 'root' -c '/usr/bin/nice -n19 /usr/bin/ionice -c2 -n7 /usr/bin/backintime --profile-id 2 backup-job >/dev/null'"

khaeusler avatar Apr 05 '24 13:04 khaeusler

Have you configured this yourself or is this generated by BIT? I ask because I have not written that part of BIT and I don't use udev. Root and the user do use the same ID_FS_UUID as backup destination?

buhtz avatar Apr 05 '24 13:04 buhtz

The udev roles were created directly via BIT; yes the same external HDD is used.

khaeusler avatar Apr 05 '24 13:04 khaeusler

Global lock settings

Can you have a look (as root & user) in the Manage profiles dialog in the Options tab and see if the option "Run only one snapshot at a time" is set?

Yes, this option is set for both profiles.

khaeusler avatar Apr 05 '24 13:04 khaeusler

Am I correct in assuming that the files/folders in source and destination of the two snapshot profiles don't actually conflict with each other, but rather you want to reduce the load on the one external hard drive, so you intend for both jobs to run one after the other?

Why not opening for just reading (r) instead of writing (w)? https://github.com/bit-team/backintime/blob/d33f35e10b4c3027b3b12881152c157eb1b2cc8c/common/snapshots.py#L2208

Regarding to my quick n dirty tests this might solve the problem. But let's wait on my team mates with some more expertise in this than me. 😃

Note to me (hint from #[email protected]): Make sure the file exist or is created when using read mode.

buhtz avatar Apr 05 '24 13:04 buhtz

Am I correct in assuming that the files/folders in source and destination of the two snapshot profiles don't actually conflict with each other, but rather you want to reduce the load on the one external hard drive, so you intend for both jobs to run one after the other?

Yes, to reduce the load. If both are running in parallel, it can happen that the load goes over 20, e.g. if I'm still on YouTube, it can jerk.

khaeusler avatar Apr 05 '24 14:04 khaeusler

Why not opening for just reading (r) instead of writing (w)?

https://github.com/bit-team/backintime/blob/d33f35e10b4c3027b3b12881152c157eb1b2cc8c/common/snapshots.py#L2208

Might work, didn't test - still does not explain (for me), why a regular user can run their backup job while root cannot. see https://github.com/bit-team/backintime/issues/1122#issuecomment-910553364

gitraphha avatar Apr 17 '24 10:04 gitraphha

Beside change the open mode from write to read another proposal could be to relocate the flock file itself.

https://github.com/bit-team/backintime/blob/d33f35e10b4c3027b3b12881152c157eb1b2cc8c/common/snapshots.py#L63

Can someone of you test it to modify that path to /run/lock/backintime.lock if the folder exist, otherwise /var/lock/backintime.lock?

My proposal

  • Move the lock file into /run/lock or /var/lock if the first does not exist. These are the recommended default locations for flock files. To my knowledge and understanding the Linux kernel's security feature is restricted to files in /tmp only.
  • Keep the write mode because this avoid to add an extra if-exist-check.
  • Realize the flock handling with a context manager. It is more pythonic with clean and isolated code.
  • Have a look at common/applicationinstance.py::ApplicationInstance.flockExclusive() and take it into account when developing the context manager. But keep that code and work on it in a separate PR. It could be said that the class ApplicationInstance is kind of an primitive context manager.
  • Update the flock docu in doc-dev if necessary.

buhtz avatar Apr 17 '24 10:04 buhtz

I opened PR #1697. Would be glad if you could give it a try and test it in your environment. Let me know if you have further questions or if I can be of assistance.

buhtz avatar Apr 21 '24 21:04 buhtz

After I copied flock.py and snapshots.py to /usr/share/backintime/common and connected the HDD, there were no more problems with the root user's backup process.

khaeusler avatar Apr 22 '24 07:04 khaeusler

@buhtz

3. As user take a snapshot and close BIT after it. The file /tmp/backintime.lock is created (-rw-rw-rw- 1 user user) and not deleted when BIT is closed.

Great work! This looks like a bug in BiT anyhow (= not releasing a lock file) and introducing a context manager instead of using our legacy ApplicationInstance seems to fix this as you mentioned in your PR comment (https://github.com/bit-team/backintime/pull/1697/files#r1573930887)

I think I need to update my locking documentation once this fix is merged...

Furthermore I am wondering if the missing access rights for root may be the reason for #1592 too because X11 requires access to a "temp" folder too and we "borrow" the folder from the user when BiT is running as "root"... I will check this once I find the time to continue working on that issue...

aryoda avatar May 03 '24 11:05 aryoda

Furthermore I am wondering if the missing access rights for root may be the reason for #1592 too because X11 requires access to a "temp" folder too and we "borrow" the folder from the user when BiT is running as "root"... I will check this once I find the time to continue working on that issue...

Might be the case. I couldn't find the document explaining (in human readable form) this Kernel security feature. I just found this commit message: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=30aba6656f61ed44cba445a3c0d38b296fa9e8f5

To my understanding this feature was introduced not long ago in the Linux kernel. It seems to be an attack vector if root writes to tmp-files that are not owned by root. So it is prohibited not matter how the "others" or "groups" permissions on such a file might be. Root is not allowed to write to files in /tmp not owned by root.

buhtz avatar May 03 '24 12:05 buhtz