| # |
| # Copyright (C) 2016 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| |
| """This file provides all ACL related parsing and behavior.""" |
| |
| |
| import os |
| import re |
| import struct |
| |
| |
| from project import common |
| from selinux import file_context |
| |
| |
| class FileAcl(object): |
| DEFAULTS = { |
| 'USER': '1000', |
| 'GROUP': '1000', |
| 'SELABEL': '', |
| 'PERMISSIONS': '0400', |
| 'CAPABILITIES': '0' |
| } |
| |
| def __init__(self, copy): |
| self._origin = common.Origin() |
| self._copy = copy |
| # For now, we default to system. |
| self._user = self.DEFAULTS['USER'] |
| self._group = self.DEFAULTS['GROUP'] |
| self._selabel = self.DEFAULTS['SELABEL'] |
| self._perms = self.DEFAULTS['PERMISSIONS'] |
| self._fcaps = self.DEFAULTS['CAPABILITIES'] |
| # For packs which wrap a Android build, we will often |
| # want to just use the build systems default values. |
| # When this is False, fs_config and file_context will |
| # return empty. |
| self._override_build = True |
| # TODO(wad): add more types (b/27848879). |
| self._file_type = file_context.FILE_TYPE_FILE |
| default_acl = None |
| if (copy is not None and |
| copy.pack is not None and |
| copy.pack.defaults is not None): |
| default_acl = copy.pack.defaults.acl |
| if default_acl is not None: |
| self._user = default_acl.user |
| self._group = default_acl.group |
| self._selabel = default_acl.selabel |
| self._perms = default_acl.perms |
| self._fcaps = default_acl.fcaps |
| |
| def load(self, ele): |
| """Loads the FileAcl from a XML element node (set-acl).""" |
| self._origin = ele.origin.copy() |
| ele.limit_attribs(['user', 'group', 'selabel', 'perms', 'fcaps']) |
| # Since all attributes are optional, we can just pull from the |
| # element attrib. |
| for key in ele.attrib: |
| if ele.attrib[key] not in (None, ''): |
| self.__dict__['_{}'.format(key)] = ele.attrib[key] |
| if self._perms is not None: |
| if not re.match('^0[0-7][0-7][0-7]$', self._perms): |
| raise common.LoadErrorWithOrigin( |
| self._origin, |
| '@perms must match ^0[0-7][0-7][0-7]$: {}'.format( |
| self._perms)) |
| # TODO(wad) Add fcaps content validation. |
| if self._fcaps != self.DEFAULTS['CAPABILITIES']: |
| raise common.LoadErrorWithOrigin( |
| self._origin, '@fcaps support is not yet implemented.') |
| |
| @property |
| def override_build(self): |
| return self._override_build |
| |
| @override_build.setter |
| def override_build(self, build_def): |
| self._override_build = build_def |
| |
| @property |
| def user(self): |
| return self._user |
| |
| @user.setter |
| def user(self, user): |
| # TODO(wad) http://b/27564772 |
| try: |
| self._user = int(user) |
| except TypeError: |
| raise ValueError('user must be numeric: {}'.format(user)) |
| |
| @property |
| def group(self): |
| return self._group |
| |
| @group.setter |
| def group(self, group): |
| # TODO(wad) http://b/27564772 |
| try: |
| self._group = int(group) |
| except TypeError: |
| raise ValueError('group must be numeric: {}'.format(group)) |
| |
| @property |
| def perms(self): |
| return self._perms |
| |
| @perms.setter |
| def perms(self, perms): |
| try: |
| self._perms = oct(int(perms, 8)) |
| except TypeError: |
| raise ValueError('perms must be octal: {}'.format(perms)) |
| |
| @property |
| def selabel(self): |
| return self._selabel |
| |
| @selabel.setter |
| def selabel(self, selabel): |
| self._selabel = selabel |
| |
| @property |
| def fcaps(self): |
| return self._fcaps |
| |
| @fcaps.setter |
| def fcaps(self, fcaps): |
| self._fcaps = fcaps |
| |
| @property |
| def copy(self): |
| return self._copy |
| |
| @copy.setter |
| def copy(self, cpy): |
| self._copy = cpy |
| |
| @property |
| def file_type(self): |
| return self._file_type |
| |
| @file_type.setter |
| def file_type(self, ftype): |
| if ftype not in file_context.FILE_TYPE.keys(): |
| raise ValueError('ftype must be one of {}: {}'.format( |
| file_context.FILE_TYPE.keys(), ftype)) |
| self._file_type = ftype |
| |
| def file_context(self, path=None, ftype=None): |
| """Builds a ASCII SELinux file context entry for the given ACL. |
| |
| If the ACL is applied to a GLOB, then path and ftype |
| should be applied or the glob will be written into the |
| file_context. |
| """ |
| if self.override_build == False: |
| return '' |
| path = path or self._copy.dst |
| if not os.path.isabs(path): |
| path = os.path.sep + path |
| ftype = ftype or file_context.FILE_TYPE[self._file_type] |
| return '{} {} {}'.format(path, ftype, self._selabel) |
| |
| def fs_config(self, path=None, binary=False): |
| """Returns the fs_config entry in ASCII or binary format. |
| |
| Args: |
| path: Optionally override the copy destination. |
| binary: If True, returns the binary fs_config format. |
| If False, returns the ASCII fs_config format. |
| |
| Note for callers: binary fs_config files will be evaluated |
| in first match order so it is important to use more specific |
| paths first. |
| """ |
| if self.override_build == False: |
| return '' |
| path = path or self._copy.dst |
| if path.startswith(os.path.sep): |
| path = path.lstrip('/') |
| if binary: |
| # https://android.googlesource.com/platform/system/core/+/master/libcutils/fs_config.c#45 |
| # struct fs_path_config_from_file { |
| # uint16_t length; /* header plus prefix */ |
| # uint16_t mode; |
| # uint16_t uid; |
| # uint16_t gid |
| # uint64_t capabilities; |
| # char prefix[]; |
| # } __attribute__((__aligned__(sizeof(uint64_t)))); |
| path_len = len(path) + 1 # Terminating NUL is required. |
| total_len = 16 + path_len |
| # The on-disk format is little endian byte order. |
| entry = struct.pack( |
| '<HHHHQ{}s'.format(path_len), total_len, |
| int(self._perms, 8), int(self._user), |
| int(self._group), int(self._fcaps), path) |
| return entry |
| return '{} {} {} {} capabilities={}'.format( |
| path, self._user, self._group, self._perms, self._fcaps) |
| |
| def __repr__(self): |
| return ('<set-acl user="{}" group="{}" selabel="{}" ' |
| 'perms="{}" fcaps="{}"/>'.format( |
| self._user or '', self._group or '', self._selabel or '', |
| self._perms or '', self._fcaps or '')) |