blob: cb0f44c7ff93bb12198e53a56850ea659a54c7d3 [file] [log] [blame]
#
# 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 ''))