duplicity-team team mailing list archive
-
duplicity-team team
-
Mailing list archive
-
Message #02909
Re: [Merge] lp:~jmwilson/duplicity/capabilities into lp:duplicity
If I understand this correctly, then new files will be created by
'duplicity', but older files owned by root will not be deletable.
Correct? I'm thinking a one-time 'chown -R duplicity: ~/cache/duplicity/*'
would fix that problem.
On Sun, Apr 26, 2015 at 7:31 PM, James Wilson <jmw@xxxxxxxxxxxx> wrote:
> James Wilson has proposed merging lp:~jmwilson/duplicity/capabilities into
> lp:duplicity.
>
> Requested reviews:
> duplicity-team (duplicity-team)
>
> For more details, see:
> https://code.launchpad.net/~jmwilson/duplicity/capabilities/+merge/257488
>
> Proposal is to add an unprivileged "duplicity" user during installation
> that is used to limit the capabilities when duplicity is run as root. To
> manage capabilities, I'm using the python bindings for libcap-ng (which
> needs its own updating since at least the current vivid package is empty
> for some reason, but is correct when built from source).
>
> I've been interested in using duplicity for system-wide backups, but one
> thing that is troubling is that it must be run as root. As an interpreted
> program that communicates on the network, this exposes the host to possible
> bugs in python or any other packages imported in duplicity.
>
> First, examining the code in bin/duplicity:
> # if python is run setuid, it's only partway set,
> # so make sure to run with euid/egid of root
> if os.geteuid() == 0:
> # make sure uid/gid match euid/egid
> os.setuid(os.geteuid())
> os.setgid(os.getegid())
>
> I'm not sure what this is for; if you're root (i.e., os.geteuid() == 0)
> then there's no need to switch the real uid. When the machination of
> "setuid(geteuid())" is used it is typically when the ruid=0 and we want to
> irrevocably drop privileges to a euid!=0 normal user. That's not the case
> here, so this doesn't help or harm us.
>
> Then there's the issue of running duplicity as root. Since it's an
> interpreted program, the normal ways of expanding privileges (SUID
> executable or setcap on the script) are unavailable, and the other options
> are to run it as root, or put SUID or file capabilities on the python
> interpreter.
>
> The only reason to run duplicity as root is get read access to the whole
> file system. We can safely drop all capabilities other than
> CAP_DAC_READ_SEARCH. Since we're still root, we'll get all capabilities
> back if we do execve, so we could also change the bounding set or lock the
> securebits to prevent the kernel from re-granting privileges on execve.
> Nonetheless, we're still root, and lots of important files are owned by and
> writable to root, so the best choice is to change uid to an unprivileged
> user who maintains only the ability to read the whole file system.
>
> It's hard to test the change directly, since it doesn't change the output
> or actions of duplicity. The following python script mimics what the code
> does and demonstrates the reduction of capabilities:
>
> #!/usr/bin/env python
>
> from __future__ import print_function
> from capng import *
> import fcntl
> import os
> import pwd
> import signal
> import sys
>
> if os.geteuid() != 0:
> sys.exit()
>
> user = pwd.getpwnam("nobody")
> capng_clear(CAPNG_SELECT_CAPS)
> capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
> CAP_DAC_READ_SEARCH)
> capng_change_id(user.pw_uid, user.pw_gid, CAPNG_DROP_SUPP_GRP)
>
> print("getuid = {}, geteuid = {}".format(os.getuid(), os.geteuid()))
> print("After dropping capabilities:")
> capng_get_caps_process()
> capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_BOTH)
> print()
>
> pread, pwrite = os.pipe()
> pid = os.fork()
> if pid == 0:
> os.close(pread)
> fcntl.fcntl(pwrite, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
> os.execl('/usr/bin/tail', '-f', '/dev/null')
> else:
> os.close(pwrite)
> os.read(pread, 1)
> capng_setpid(pid)
> capng_get_caps_process()
> print("getuid = {}, geteuid = {}".format(os.getuid(), os.geteuid()))
> print("Capabilities after execve:")
> caps = capng_print_caps_numeric(CAPNG_PRINT_STDOUT, CAPNG_SELECT_BOTH)
> os.kill(pid, signal.SIGTERM)
>
> which when run as root (sudo python script.py) should produce this output:
> getuid = 65534, geteuid = 65534
> After dropping capabilities:
> Effective: 00000000, 00000004
> Permitted: 00000000, 00000004
> Inheritable: 00000000, 00000000
> Bounding Set: 0000003F, FFFFFFFF
>
> getuid = 65534, geteuid = 65534
> Capabilities after execve:
> Effective: 00000000, 00000000
> Permitted: 00000000, 00000000
> Inheritable: 00000000, 00000000
> Bounding Set: 0000003F, FFFFFFFF
>
> --
> Your team duplicity-team is requested to review the proposed merge of
> lp:~jmwilson/duplicity/capabilities into lp:duplicity.
>
> === modified file 'README'
> --- README 2014-10-18 19:44:29 +0000
> +++ README 2015-04-27 00:30:33 +0000
> @@ -23,6 +23,7 @@
> * librsync v0.9.6 or later
> * GnuPG v1.x for encryption
> * python-lockfile for concurrency locking
> + * python-capng for managing capabilities when run as root
> * for scp/sftp -- python-paramiko and python-pycryptopp
> * for ftp -- lftp version 3.7.15 or later
> * Boto 2.0 or later for single-processing S3 or GCS access (default)
>
> === modified file 'bin/duplicity'
> --- bin/duplicity 2015-03-09 18:50:58 +0000
> +++ bin/duplicity 2015-04-27 00:30:33 +0000
> @@ -38,6 +38,8 @@
> import resource
> import re
> import threading
> +import capng
> +import pwd
> from datetime import datetime
> from lockfile import FileLock
>
> @@ -1340,12 +1342,18 @@
> See https://bugs.launchpad.net/duplicity/+bug/931175
> """), log.ErrorCode.pythonoptimize_set)
>
> - # if python is run setuid, it's only partway set,
> - # so make sure to run with euid/egid of root
> + # if python is running as root, then drop all capabilities except
> + # unrestricted read access and then change user to prevent regaining
> + # capabilities via execve.
> if os.geteuid() == 0:
> - # make sure uid/gid match euid/egid
> - os.setuid(os.geteuid())
> - os.setgid(os.getegid())
> + user = pwd.getpwnam("duplicity")
> + capng.capng_clear(capng.CAPNG_SELECT_CAPS)
> + if (capng.capng_update(capng.CAPNG_ADD,
> + capng.CAPNG_EFFECTIVE |
> capng.CAPNG_PERMITTED,
> + capng.CAP_DAC_READ_SEARCH)
> + or capng.capng_change_id(user.pw_uid, user.pw_gid,
> + capng.CAPNG_DROP_SUPP_GRP)):
> + log.FatalError("Unable to drop root privileges.")
>
> # set the current time strings (make it available for command line
> processing)
> dup_time.setcurtime()
>
> === modified file 'debian/control'
> --- debian/control 2014-10-27 14:15:52 +0000
> +++ debian/control 2015-04-27 00:30:33 +0000
> @@ -27,6 +27,7 @@
> gnupg,
> python-lockfile,
> python-pexpect,
> + python-capng,
> Suggests: ncftp,
> python-boto,
> python-paramiko,
>
> === added file 'debian/duplicity.postinst'
> --- debian/duplicity.postinst 1970-01-01 00:00:00 +0000
> +++ debian/duplicity.postinst 2015-04-27 00:30:33 +0000
> @@ -0,0 +1,7 @@
> +#!/bin/sh -e
> +
> +if [ "$1" = "configure" ]; then
> + if ! getent passwd duplicity >/dev/null; then
> + adduser --quiet --system --no-create-home --home /nonexistant --shell
> /usr/sbin/nologin duplicity
> + fi
> +fi
>
>
> _______________________________________________
> Mailing list: https://launchpad.net/~duplicity-team
> Post to : duplicity-team@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~duplicity-team
> More help : https://help.launchpad.net/ListHelp
>
>
--
https://code.launchpad.net/~jmwilson/duplicity/capabilities/+merge/257488
Your team duplicity-team is requested to review the proposed merge of lp:~jmwilson/duplicity/capabilities into lp:duplicity.
Follow ups
References