Fix capabilities extraction on Linux

They are extracted correctly, for a little while at least, since chown()
*resets* all capabilities on the chowned file. Which I find curious,
since chown() is a privileged syscall. Probably a safeguard for
sysadmins who are unaware of capabilities.

The solution is to set the xattrs last, after chown()ing files.
This commit is contained in:
Marian Beermann 2016-04-16 23:52:16 +02:00
parent 8662202486
commit 39a40cd7b7
No known key found for this signature in database
GPG key ID: 9B8450B91D1362C1
3 changed files with 52 additions and 11 deletions

View file

@ -372,17 +372,6 @@ Number of files: {0.stats.nfiles}'''.format(
raise Exception('Unknown archive item type %r' % item[b'mode'])
def restore_attrs(self, path, item, symlink=False, fd=None):
xattrs = item.get(b'xattrs', {})
for k, v in xattrs.items():
try:
xattr.setxattr(fd or path, k, v, follow_symlinks=False)
except OSError as e:
if e.errno not in (errno.ENOTSUP, errno.EACCES, ):
# only raise if the errno is not on our ignore list:
# ENOTSUP == xattrs not supported here
# EACCES == permission denied to set this specific xattr
# (this may happen related to security.* keys)
raise
uid = gid = None
if not self.numeric_owner:
uid = user2uid(item[b'user'])
@ -420,6 +409,19 @@ Number of files: {0.stats.nfiles}'''.format(
os.lchflags(path, item[b'bsdflags'])
except OSError:
pass
# chown removes Linux capabilities, so set the extended attributes at the end, after chown, since they include
# the Linux capabilities in the "security.capability" attribute.
xattrs = item.get(b'xattrs', {})
for k, v in xattrs.items():
try:
xattr.setxattr(fd or path, k, v, follow_symlinks=False)
except OSError as e:
if e.errno not in (errno.ENOTSUP, errno.EACCES):
# only raise if the errno is not on our ignore list:
# ENOTSUP == xattrs not supported here
# EACCES == permission denied to set this specific xattr
# (this may happen related to security.* keys)
raise
def rename(self, name):
if name in self.manifest.archives:

View file

@ -639,6 +639,27 @@ class ArchiverTestCase(ArchiverTestCaseBase):
self.assert_equal(sorted(os.listdir('output/input/taggedall')),
['.NOBACKUP1', '.NOBACKUP2', 'CACHEDIR.TAG', ])
@pytest.mark.skipif(not xattr.XATTR_FAKEROOT, reason='Linux capabilities test, requires fakeroot >= 1.20.2')
def test_extract_capabilities(self):
fchown = os.fchown
# We need to manually patch chown to get the behaviour Linux has, since fakeroot does not
# accurately model the interaction of chown(2) and Linux capabilities, i.e. it does not remove them.
def patched_fchown(fd, uid, gid):
xattr.setxattr(fd, 'security.capability', None, follow_symlinks=False)
fchown(fd, uid, gid)
# The capability descriptor used here is valid and taken from a /usr/bin/ping
capabilities = b'\x01\x00\x00\x02\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
self.create_regular_file('file')
xattr.setxattr('input/file', 'security.capability', capabilities)
self.cmd('init', self.repository_location)
self.cmd('create', self.repository_location + '::test', 'input')
with changedir('output'):
with patch.object(os, 'fchown', patched_fchown):
self.cmd('extract', self.repository_location + '::test')
assert xattr.getxattr('input/file', 'security.capability') == capabilities
def test_path_normalization(self):
self.cmd('init', self.repository_location)
self.create_regular_file('dir1/dir2/file', size=1024 * 80)

View file

@ -2,10 +2,12 @@
"""
import errno
import os
import subprocess
import sys
import tempfile
from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno
from ctypes.util import find_library
from distutils.version import LooseVersion
from .logger import create_logger
logger = create_logger()
@ -46,6 +48,22 @@ if libc_name is None:
logger.error(msg)
raise Exception(msg)
# If we are running with fakeroot on Linux, then use the xattr functions of fakeroot. This is needed by
# the 'test_extract_capabilities' test, but also allows xattrs to work with fakeroot on Linux in normal use.
# TODO: Check whether fakeroot supports xattrs on all platforms supported below.
# TODO: If that's the case then we can make Borg fakeroot-xattr-compatible on these as well.
LD_PRELOAD = os.environ.get('LD_PRELOAD', '')
XATTR_FAKEROOT = False
if sys.platform.startswith('linux') and 'fakeroot' in LD_PRELOAD:
fakeroot_version = LooseVersion(subprocess.check_output(['fakeroot', '-v']).decode('ascii').split()[-1])
if fakeroot_version >= LooseVersion("1.20.2"):
# 1.20.2 has been confirmed to have xattr support
# 1.18.2 has been confirmed not to have xattr support
# Versions in-between are unknown
libc_name = LD_PRELOAD
XATTR_FAKEROOT = True
try:
libc = CDLL(libc_name, use_errno=True)
except OSError as e: