← Back to team overview

duplicity-team team mailing list archive

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