blob: 18a1fdc5ec04652a58ee7e029e79eb499180a041 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
__author__ = 'kdlucas@chromium.org (Kelly Lucas)'
import logging
import os
import re
import stat
import subprocess
from autotest_lib.client.bin import utils, test
from autotest_lib.client.common_lib import error
class platform_FilePerms(test.test):
"""
Test file permissions.
"""
version = 1
mtab_path = '/etc/mtab'
mount_path = '/bin/mount'
def checkid(self, fs, userid):
"""
Check that the uid and gid for fs match userid.
Args:
fs: string, directory or file path.
userid: userid to check for.
Returns:
int, the number errors (non-matches) detected.
"""
errors = 0
uid = os.stat(fs)[stat.ST_UID]
gid = os.stat(fs)[stat.ST_GID]
if userid != uid:
logging.warn('fs %s uid wrong' % fs)
errors += 1
if userid != gid:
logging.warn('fs %s gid wrong' % fs)
errors += 1
return errors
def get_perm(self, fs):
"""
Check the file permissions of filesystem.
Args:
fs: string, mount point for filesystem to check.
Returns:
int, equivalent to unix permissions.
"""
MASK = 0777
fstat = os.stat(fs)
mode = fstat[stat.ST_MODE]
fperm = oct(mode & MASK)
return fperm
def read_mtab(self):
"""
Helper function to read the mtab file into a dict
Args:
(none)
Returns:
dict, mount points as keys, and another dict with
options list, device and type as values.
"""
file_handle = open(self.mtab_path, 'r')
lines = file_handle.readlines()
file_handle.close()
comment_re = re.compile("#.*$")
mounts = {}
for line in lines:
# remove any comments first
line = comment_re.sub("", line)
fields = line.split()
# ignore malformed lines
if len(fields) < 4:
continue
# Don't include rootfs in the list, because it maps to the
# same location as /dev/root: '/' (and we don't care about
# its options at the moment).
if fields[0] == 'rootfs':
continue
mounts[fields[1]] = {'device': fields[0],
'type': fields[2],
'options': fields[3].split(',')}
return mounts
def try_write(self, fs):
"""
Try to write a file in the given filesystem.
Args:
fs: string, file system to use.
Returns:
int, number of errors encountered:
0 = write successful,
>0 = write not successful.
"""
TEXT = 'This is filler text for a test file.\n'
tempfile = os.path.join(fs, 'test')
try:
fh = open(tempfile, 'w')
fh.write(TEXT)
fh.close()
except OSError: # This error will occur with read only filesystem.
return 1
except IOError, e:
return 1
if os.path.exists(tempfile):
os.remove(tempfile)
return 0
def check_mounted_read_only(self, filesystem):
"""
Check the permissions of a filesystem according to /etc/mtab.
Args:
filesystem: string, file system device to check.
Returns:
1 if rw, 0 if ro
"""
errors = 0
mtab = self.read_mtab()
if not (filesystem in mtab.keys()):
logging.warn('Did not find filesystem %s in mtab' % filesystem)
errors += 1
return errors # no point in continuing this test.
if not ('ro' in mtab[filesystem]['options']):
logging.warn('Filesystem "%s" is not mounted read only!' %
filesystem)
errors += 1
return errors
def check_mount_setup(self):
"""
Check to make sure that mount shows the same mount points and
options as /etc/mtab, and that /etc/mtab is a symlink to
/proc/mount.
Args:
(none)
Returns:
int, Number of errors encountered.
"""
# Call mount, and compare output to /etc/mtab entries.
mount_call = subprocess.Popen([self.mount_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(out, err) = mount_call.communicate();
mount_lines = out.split("\n");
mounts = {}
for line in mount_lines:
fields = line.split()
# Skip empty/non-conforming lines.
if (len(fields) >= 6):
mounts[fields[2]] = {
'device': fields[0],
'type': fields[4],
'options': fields[5].strip("()").split(','),
}
# Compare the filesystems to make sure that what mount
# reports, and what's in the config file are identical in both
# the mounts that exist, and the options that are set. Note
# that this test allows there to be listings in mtab that are
# not currently mounted.
errors = 0
mtab = self.read_mtab()
for filesystem in mounts.keys():
if not (filesystem in mtab.keys()):
logging.warn('Mounted filesystem %s not found in mtab.' %
filesystem)
errors += 1
for option in mounts[filesystem]['options']:
if not (option in mtab[filesystem]['options']):
logging.warn('Mounted filesystem %s has option %s '
'that is not listed in mtab.' %
(filesystem, option))
errors +=1
for option in mtab[filesystem]['options']:
if not (option in mounts[filesystem]['options']):
logging.warn('Mounted filesystem %s does not have '
'option %s that is listed in mtab.' %
(filesystem, option))
errors +=1
# Now we just make sure that /etc/mtab is a symlink to /proc/mounts
mtab_link = os.readlink("/etc/mtab")
if mtab_link != "/proc/mounts":
logging.warn('Symbolic link /etc/mtab points to "%s" instead of '
'/proc/mounts.' % mtab_link)
errors += 1
return errors
def check_mount_options(self):
"""
Check the permissions of all non-rootfs filesystems to make
sure they have the right mount options.
Skips the root filesystem, and allows "/dev" to be missing
"nodev".
Args:
(none)
Returns:
int, number of filesystems with the wrong options.
"""
errors = 0
mtab = self.read_mtab()
for filesystem in mtab.keys():
# skip the rootfs mounts, because it needs to have all
# three attributes (exec, suid, and dev).
if filesystem == "/":
continue
for option in ['noexec', 'nosuid', 'nodev']:
# Let the /dev partition have dev nodes (duh!)
if option == "nodev" and filesystem == "/dev":
continue
# TODO(wad): Disable this check until it's fixed.
# See crosbug.com/2285.
#if not (option in mtab[filesystem]['options']):
# logging.warn("%s partition doesn't have option %s set" %
# (filesystem, option))
# errors += 1
return errors
def run_once(self):
"""
Main testing routine for platform_FilePerms.
"""
errors = 0
# Root owned directories with expected permissions.
root_dirs = {'/': '0755',
'/bin': '0755',
'/boot': '0755',
'/dev': '0755',
'/etc': '0755',
'/home': '0755',
'/lib': '0755',
'/media': '0777',
'/mnt': '0755',
'/mnt/stateful_partition': '0755',
'/opt': '0755',
'/proc': '0555',
'/sbin': '0755',
'/sys': '0755',
'/tmp': '0777',
'/usr': '0755',
'/usr/bin': '0755',
'/usr/lib': '0755',
'/usr/local': '0755',
'/usr/sbin': '0755',
'/usr/share': '0755',
'/var': '0755',
'/var/cache': '0755'}
# Read-only directories
ro_dirs = ['/', '/bin', '/boot', '/etc', '/lib', '/mnt',
'/opt', '/sbin', '/usr', '/usr/bin', '/usr/lib',
'/usr/sbin', '/usr/share']
# Root directories writable by root
root_rw_dirs = ['/var', '/var/lib', '/var/cache', '/var/log',
'/usr/local']
# Ensure you cannot write files in read only directories.
for dir in ro_dirs:
if self.try_write(dir) == 0:
logging.warn('Root can write to RO dir %s' % dir)
errors += 1
# Ensure the uid and gid are correct for root owned directories.
for dir in root_dirs:
if self.checkid(dir, 0) > 0:
errors += 1
# Ensure root can write into root dirs with rw access.
for dir in root_rw_dirs:
if self.try_write(dir) > 0:
logging.warn('Root cannot write RW dir %s' % dir)
errors += 1
# Check permissions on root owned directories.
for dir in root_dirs:
fperms = self.get_perm(dir)
if fperms != root_dirs[dir]:
logging.warn('%s has %s permissions' % (dir, fperms))
errors += 1
errors += self.check_mounted_read_only('/')
# Check mount options on mount points.
errors += self.check_mount_options()
# Check that /bin/mount output and mtab jive,
# and that mtab is a symbolic link to /dev/mounts.
errors += self.check_mount_setup()
# If errors is not zero, there were errors.
if errors > 0:
raise error.TestFail('Found %d permission errors' % errors)