forked from pool/rpmlint
de6c5c2d36
Accepted submit request 53336 from user lnussel OBS-URL: https://build.opensuse.org/request/show/53336 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/rpmlint?expand=0&rev=82
262 lines
9.3 KiB
Python
262 lines
9.3 KiB
Python
# vim:sw=4:et
|
|
#############################################################################
|
|
# File : CheckSUIDPermissions.py
|
|
# Package : rpmlint
|
|
# Author : Ludwig Nussel
|
|
# Purpose : Check for /etc/permissions violations
|
|
#############################################################################
|
|
|
|
from Filter import *
|
|
import AbstractCheck
|
|
import re
|
|
import os
|
|
import string
|
|
import rpm
|
|
|
|
_permissions_d_whitelist = (
|
|
"lprng",
|
|
"lprng.paranoid",
|
|
"mail-server",
|
|
"mail-server.paranoid",
|
|
"postfix",
|
|
"postfix.paranoid",
|
|
"sendmail",
|
|
"sendmail.paranoid",
|
|
"squid",
|
|
"texlive",
|
|
"texlive.paranoid",
|
|
)
|
|
|
|
class SUIDCheck(AbstractCheck.AbstractCheck):
|
|
def __init__(self):
|
|
AbstractCheck.AbstractCheck.__init__(self, "CheckSUIDPermissions")
|
|
self.perms = {}
|
|
files = [ "/etc/permissions", "/etc/permissions.secure" ]
|
|
|
|
for file in files:
|
|
if os.path.exists(file):
|
|
self._parsefile(file)
|
|
|
|
def _parsefile(self,file):
|
|
lnr = 0
|
|
lastfn = None
|
|
for line in open(file):
|
|
lnr+=1
|
|
line = line.split('#')[0].split('\n')[0]
|
|
line = line.lstrip()
|
|
if not len(line):
|
|
continue
|
|
|
|
if line.startswith("+capabilities "):
|
|
line = line[len("+capabilities "):]
|
|
if lastfn:
|
|
self.perms[lastfn]['fscaps'] = line
|
|
continue
|
|
|
|
line = re.split(r'\s+', line)
|
|
if len(line) == 3:
|
|
fn = line[0]
|
|
owner = line[1].replace('.', ':')
|
|
mode = line[2]
|
|
|
|
self.perms[fn] = { "owner" : owner, "mode" : int(mode,8)&07777}
|
|
# for permissions that don't change and therefore
|
|
# don't need special handling
|
|
if file == '/etc/permissions':
|
|
self.perms[fn]['static'] = True
|
|
else:
|
|
print >>sys.stderr, "invalid line %d " % lnr
|
|
|
|
def check(self, pkg):
|
|
global _permissions_d_whitelist
|
|
|
|
if pkg.isSource():
|
|
return
|
|
|
|
files = pkg.files()
|
|
|
|
permfiles = {}
|
|
# first pass, find and parse permissions.d files
|
|
for f in files.keys():
|
|
if f in pkg.ghostFiles():
|
|
continue
|
|
|
|
if f.startswith("/etc/permissions.d/"):
|
|
|
|
bn = f[19:]
|
|
if not bn in _permissions_d_whitelist:
|
|
printError(pkg, "permissions-unauthorized-file", f)
|
|
|
|
bn = bn.split('.')[0]
|
|
if not bn in permfiles:
|
|
permfiles[bn] = 1
|
|
|
|
for f in permfiles:
|
|
f = pkg.dirName() + "/etc/permissions.d/" + f
|
|
if os.path.exists(f+".secure"):
|
|
self._parsefile(f + ".secure")
|
|
else:
|
|
self._parsefile(f)
|
|
|
|
need_set_permissions = False
|
|
found_suseconfig = False
|
|
# second pass, find permissions violations
|
|
for f, pkgfile in files.items():
|
|
|
|
if pkgfile.filecaps:
|
|
printError(pkg, 'permissions-fscaps', '%(file)s has fscaps "%(caps)s"' % \
|
|
{ 'file':f, 'caps':pkgfile.filecaps})
|
|
|
|
mode = pkgfile.mode
|
|
owner = pkgfile.user+':'+pkgfile.group
|
|
|
|
# S_IFSOCK 014 socket
|
|
# S_IFLNK 012 symbolic link
|
|
# S_IFREG 010 regular file
|
|
# S_IFBLK 006 block device
|
|
# S_IFDIR 004 directory
|
|
# S_IFCHR 002 character device
|
|
# S_IFIFO 001 FIFO
|
|
type = (mode>>12)&017;
|
|
mode &= 07777
|
|
need_verifyscript = False
|
|
if f in self.perms or (type == 04 and f+"/" in self.perms):
|
|
if type == 012:
|
|
printWarning(pkg, "permissions-symlink", f)
|
|
continue
|
|
|
|
need_verifyscript = True
|
|
|
|
m = 0
|
|
o = "invalid"
|
|
if type == 04:
|
|
if f in self.perms:
|
|
printWarning(pkg, 'permissions-dir-without-slash', f)
|
|
else:
|
|
f += '/'
|
|
|
|
m = self.perms[f]['mode']
|
|
o = self.perms[f]['owner']
|
|
|
|
if mode != m:
|
|
printError(pkg, 'permissions-incorrect', '%(file)s has mode 0%(mode)o but should be 0%(m)o' % \
|
|
{ 'file':f, 'mode':mode, 'm':m })
|
|
|
|
if owner != o:
|
|
printError(pkg, 'permissions-incorrect-owner', '%(file)s belongs to %(owner)s but should be %(o)s' % \
|
|
{ 'file':f, 'owner':owner, 'o':o })
|
|
|
|
elif type != 012:
|
|
|
|
if f+'/' in self.perms:
|
|
printWarning(pkg, 'permissions-file-as-dir', f+' is a file but listed as directory')
|
|
|
|
if mode&06000:
|
|
need_verifyscript = True
|
|
msg = '%(file)s is packaged with setuid/setgid bits (0%(mode)o)' % { 'file':f, 'mode':mode }
|
|
if type != 04:
|
|
printError(pkg, 'permissions-file-setuid-bit', msg)
|
|
else:
|
|
printWarning(pkg, 'permissions-directory-setuid-bit', msg)
|
|
|
|
if mode&02:
|
|
need_verifyscript = True
|
|
printError(pkg, 'permissions-world-writable', \
|
|
'%(file)s is packaged with world writable permissions (0%(mode)o)' % \
|
|
{ 'file':f, 'mode':mode })
|
|
|
|
if need_verifyscript and \
|
|
(not f in self.perms or not 'static' in self.perms[f]):
|
|
need_set_permissions = True
|
|
script = pkg[rpm.RPMTAG_VERIFYSCRIPT] or pkg[rpm.RPMTAG_VERIFYSCRIPTPROG]
|
|
|
|
found = False
|
|
if script:
|
|
for line in script.split("\n"):
|
|
if "/chkstat" in line and f in line:
|
|
found = True
|
|
break
|
|
|
|
if not script or not found:
|
|
printWarning(pkg, 'permissions-missing-verifyscript', \
|
|
"missing %%verify_permissions -e %s" % f)
|
|
|
|
|
|
script = pkg[rpm.RPMTAG_POSTIN] or pkg[rpm.RPMTAG_POSTINPROG]
|
|
found = False
|
|
if script:
|
|
for line in script.split("\n"):
|
|
if "chkstat -n" in line and f in line:
|
|
found = True
|
|
break
|
|
|
|
if "SuSEconfig --module permissions" in line:
|
|
found = True
|
|
found_suseconfig = True
|
|
break
|
|
|
|
if not script and not found:
|
|
printError(pkg, 'permissions-missing-postin', \
|
|
"missing %%set_permissions %s in %%post" % f)
|
|
|
|
if need_set_permissions:
|
|
if not 'permissions' in map(lambda x: x[0], pkg.prereq()):
|
|
printError(pkg, 'permissions-missing-requires', \
|
|
"missing 'permissions' in PreReq")
|
|
|
|
if found_suseconfig:
|
|
printInfo(pkg, 'permissions-suseconfig-obsolete', \
|
|
"%run_permissions is obsolete")
|
|
|
|
check=SUIDCheck()
|
|
|
|
if Config.info:
|
|
addDetails(
|
|
'permissions-unauthorized-file',
|
|
"""If the package is intended for inclusion in any SUSE product
|
|
please open a bug report to request review of the package by the
|
|
security team""",
|
|
'permissions-symlink',
|
|
"""permissions handling for symlinks is useless. Please contact
|
|
security@suse.de to remove the entry.""",
|
|
'permissions-dir-without-slash',
|
|
"""the entry in the permissions file refers to a directory. Please
|
|
contact security@suse.de to append a slash to the entry in order to
|
|
avoid security problems.""",
|
|
'permissions-file-as-dir',
|
|
"""the entry in the permissions file refers to a directory but the
|
|
package actually contains a file. Please contact security@suse.de to
|
|
remove the slash.""",
|
|
'permissions-incorrect',
|
|
"""please use the %attr macro to set the correct permissions.""",
|
|
'permissions-incorrect-owner',
|
|
"""please use the %attr macro to set the correct ownership.""",
|
|
'permissions-file-setuid-bit',
|
|
"""If the package is intended for inclusion in any SUSE product
|
|
please open a bug report to request review of the program by the
|
|
security team""",
|
|
'permissions-directory-setuid-bit',
|
|
"""If the package is intended for inclusion in any SUSE product
|
|
please open a bug report to request review of the package by the
|
|
security team""",
|
|
'permissions-world-writable',
|
|
"""If the package is intended for inclusion in any SUSE product
|
|
please open a bug report to request review of the package by the
|
|
security team""",
|
|
'permissions-fscaps',
|
|
"""Packaging file capabilities is currently not supported. Please
|
|
use normal permissions instead. You may contact the security team to
|
|
request an entry that sets capabilities in /etc/permissions
|
|
instead.""",
|
|
'permissions-missing-postin',
|
|
"""Please add an appropriate %post section""",
|
|
'permissions-missing-requires',
|
|
"""Please add \"PreReq: permissions\"""",
|
|
'permissions-missing-verifyscript',
|
|
"""Please add a %verifyscript section""",
|
|
'permissions-suseconfig-obsolete',
|
|
"""The %run_permissions macro calls SuSEconfig which sets permissions for all
|
|
files in the system. Please use %set_permissions <filename> instead
|
|
to only set permissions for files contained in this package""",
|
|
)
|