1
# ----------------------------------------------------------------------
2
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of version 2 of the GNU General Public
6
# License as published by the Free Software Foundation.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# ----------------------------------------------------------------------
14
# No old version logs, only 2.6 + supported
15
from __future__ import with_statement
27
import apparmor.config
28
import apparmor.logparser
29
import apparmor.severity
31
from copy import deepcopy
33
from apparmor.common import (AppArmorException, open_file_read, valid_path, hasher,
34
open_file_write, convert_regexp, DebugLogger)
36
import apparmor.ui as aaui
38
from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode,
39
mode_to_str_user, mode_contains, AA_OTHER,
40
flatten_mode, owner_flatten_mode)
42
import apparmor.rules as aarules
44
from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast
46
# setup module translations
47
from apparmor.translations import init_translation
48
_ = init_translation()
50
# Setup logging incase of debugging is enabled
51
debug_logger = DebugLogger('aa')
53
CONFDIR = '/etc/apparmor'
54
running_under_genprof = False
55
unimplemented_warning = False
57
# The database for severity
59
# The file to read log messages from
70
extra_profile_dir = None
72
# To keep track of previously included profile fragments
75
existing_profiles = dict()
77
seen_events = 0 # was our
78
# To store the globs entered by users so they can be provided again
81
## Variables used under logprof
84
transitions = hasher()
85
aa = hasher() # Profiles originally in sd, replace by aa
86
original_aa = hasher()
87
extras = hasher() # Inactive profiles from extras
92
seen = hasher() # dir()
93
profile_changes = hasher()
95
log_dict = hasher() # dict()
99
helpers = dict() # Preserve this between passes # was our
102
filelist = hasher() # File level variables and stuff in config files
105
"""Shutdowns the logger and records exit if debugging enabled"""
106
debug_logger.debug('Exiting..')
107
debug_logger.shutdown()
109
# Register the on_exit method with atexit
110
atexit.register(on_exit)
112
def check_for_LD_XXX(file):
113
"""Returns True if specified program contains references to LD_PRELOAD or
114
LD_LIBRARY_PATH to give the Px/Ux code better suggestions"""
116
if not os.path.isfile(file):
118
size = os.stat(file).st_size
119
# Limit to checking files under 100k for the sake of speed
122
with open_file_read(file, encoding='ascii') as f_in:
124
if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line:
128
def fatal_error(message):
129
# Get the traceback to the message
130
tb_stack = traceback.format_list(traceback.extract_stack())
131
tb_stack = ''.join(tb_stack)
132
# Append the traceback to message
133
message = message + '\n' + tb_stack
134
debug_logger.error(message)
135
caller = inspect.stack()[1][3]
137
# If caller is SendDataToYast or GetDatFromYast simply exit
138
if caller == 'SendDataToYast' or caller == 'GetDatFromYast':
141
# Else tell user what happened
142
aaui.UI_Important(message)
146
def check_for_apparmor():
147
"""Finds and returns the mointpoint for apparmor None otherwise"""
148
filesystem = '/proc/filesystems'
149
mounts = '/proc/mounts'
150
support_securityfs = False
152
regex_securityfs = re.compile('^\S+\s+(\S+)\s+securityfs\s')
153
if valid_path(filesystem):
154
with open_file_read(filesystem) as f_in:
156
if 'securityfs' in line:
157
support_securityfs = True
158
if valid_path(mounts):
159
with open_file_read(mounts) as f_in:
161
if support_securityfs:
162
match = regex_securityfs.search(line)
164
mountpoint = match.groups()[0] + '/apparmor'
165
if valid_path(mountpoint):
166
aa_mountpoint = mountpoint
167
# Check if apparmor is actually mounted there
168
if not valid_path(aa_mountpoint + '/profiles'):
173
"""Returns the executable fullpath for the file, None otherwise"""
174
if sys.version_info >= (3, 3):
175
return shutil.which(file)
176
env_dirs = os.getenv('PATH').split(':')
177
for env_dir in env_dirs:
178
env_path = env_dir + '/' + file
179
# Test if the path is executable or not
180
if os.access(env_path, os.X_OK):
184
def get_full_path(original_path):
185
"""Return the full path after resolving any symlinks"""
188
if not path.startswith('/'):
189
path = os.getcwd() + '/' + path
190
while os.path.islink(path):
193
fatal_error(_("Followed too many links while resolving %s") % (original_path))
194
direc, file = os.path.split(path)
195
link = os.readlink(path)
196
# If the link an absolute path
197
if link.startswith('/'):
200
# Link is relative path
201
path = direc + '/' + link
202
return os.path.realpath(path)
204
def find_executable(bin_path):
205
"""Returns the full executable path for the given executable, None otherwise"""
207
if os.path.exists(bin_path):
208
full_bin = get_full_path(bin_path)
210
if '/' not in bin_path:
211
env_bin = which(bin_path)
213
full_bin = get_full_path(env_bin)
214
if full_bin and os.path.exists(full_bin):
218
def get_profile_filename(profile):
219
"""Returns the full profile name"""
220
if existing_profiles.get(profile, False):
221
return existing_profiles[profile]
222
elif profile.startswith('/'):
224
profile = profile[1:]
226
profile = "profile_" + profile
227
profile = profile.replace('/', '.')
228
full_profilename = profile_dir + '/' + profile
229
return full_profilename
231
def name_to_prof_filename(prof_filename):
232
"""Returns the profile"""
233
if prof_filename.startswith(profile_dir):
234
profile = prof_filename.split(profile_dir, 1)[1]
235
return (prof_filename, profile)
237
bin_path = find_executable(prof_filename)
239
prof_filename = get_profile_filename(bin_path)
240
if os.path.isfile(prof_filename):
241
return (prof_filename, bin_path)
246
"""Sets the profile to complain mode if it exists"""
247
prof_filename, name = name_to_prof_filename(path)
248
if not prof_filename:
249
fatal_error(_("Can't find %s") % path)
250
set_complain(prof_filename, name)
253
"""Sets the profile to enforce mode if it exists"""
254
prof_filename, name = name_to_prof_filename(path)
255
if not prof_filename:
256
fatal_error(_("Can't find %s") % path)
257
set_enforce(prof_filename, name)
259
def set_complain(filename, program):
260
"""Sets the profile to complain mode"""
261
aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program))
262
# a force-complain symlink is more packaging-friendly, but breaks caching
263
# create_symlink('force-complain', filename)
264
change_profile_flags(filename, program, 'complain', True)
266
def set_enforce(filename, program):
267
"""Sets the profile to enforce mode"""
268
aaui.UI_Info(_('Setting %s to enforce mode.') % (filename if program is None else program))
269
delete_symlink('force-complain', filename)
270
delete_symlink('disable', filename)
271
change_profile_flags(filename, program, 'complain', False)
273
def delete_symlink(subdir, filename):
275
link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
276
if link != path and os.path.islink(link):
279
def create_symlink(subdir, filename):
281
bname = os.path.basename(filename)
283
raise AppArmorException(_('Unable to find basename for %s.') % filename)
285
link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path)
287
#link = link + '/%s'%bname
289
symlink_dir = os.path.dirname(link)
290
if not os.path.exists(symlink_dir):
291
# If the symlink directory does not exist create it
292
os.makedirs(symlink_dir)
294
if not os.path.exists(link):
296
os.symlink(filename, link)
298
raise AppArmorException(_('Could not create %s symlink to %s.') % (link, filename))
301
"""Returns the first/head line of the file"""
303
if os.path.isfile(file):
304
with open_file_read(file) as f_in:
306
first = f_in.readline().rstrip()
307
except UnicodeDecodeError:
311
raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file)
313
def get_output(params):
314
"""Returns the return code output by running the program with the args given in the list"""
319
# program is executable
320
if os.access(program, os.X_OK):
322
# Get the output of the program
323
output = subprocess.check_output(params)
325
raise AppArmorException(_("Unable to fork: %s\n\t%s") % (program, str(e)))
326
# If exit-codes besides 0
327
except subprocess.CalledProcessError as e:
329
output = output.decode('utf-8').split('\n')
333
output = output.decode('utf-8').split('\n')
334
# Remove the extra empty string caused due to \n if present
340
"""Returns a list of paths from ldd output"""
341
pattern1 = re.compile('^\s*\S+ => (\/\S+)')
342
pattern2 = re.compile('^\s*(\/\S+)')
344
ret, ldd_out = get_output([ldd, file])
347
if 'not a dynamic executable' in line:
349
if 'cannot read header' in line:
351
if 'statically linked' in line:
353
match = pattern1.search(line)
355
reqs.append(match.groups()[0])
357
match = pattern2.search(line)
359
reqs.append(match.groups()[0])
362
def handle_binfmt(profile, path):
363
"""Modifies the profile to add the requirements"""
364
reqs_processed = dict()
365
reqs = get_reqs(path)
368
if not reqs_processed.get(library, False):
369
if get_reqs(library):
370
reqs += get_reqs(library)
371
reqs_processed[library] = True
372
combined_mode = match_prof_incs_to_path(profile, 'allow', library)
375
library = glob_common(library)
378
profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr')
379
profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set())
381
def get_inactive_profile(local_profile):
382
if extras.get(local_profile, False):
383
return {local_profile: extras[local_profile]}
386
def create_new_profile(localfile):
387
local_profile = hasher()
388
local_profile[localfile]['flags'] = 'complain'
389
local_profile[localfile]['include']['abstractions/base'] = 1
391
if os.path.exists(localfile) and os.path.isfile(localfile):
392
hashbang = head(localfile)
393
if hashbang.startswith('#!'):
394
interpreter_path = get_full_path(hashbang.lstrip('#!').strip())
396
interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path)
398
local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r')
400
local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set())
402
local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix')
404
local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set())
406
if interpreter == 'perl':
407
local_profile[localfile]['include']['abstractions/perl'] = True
408
elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter):
409
local_profile[localfile]['include']['abstractions/python'] = True
410
elif interpreter == 'ruby':
411
local_profile[localfile]['include']['abstractions/ruby'] = True
412
elif interpreter in ['bash', 'dash', 'sh']:
413
local_profile[localfile]['include']['abstractions/bash'] = True
414
handle_binfmt(local_profile[localfile], interpreter_path)
417
local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr')
419
local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set())
421
handle_binfmt(local_profile[localfile], localfile)
422
# Add required hats to the profile if they match the localfile
423
for hatglob in cfg['required_hats'].keys():
424
if re.search(hatglob, localfile):
425
for hat in sorted(cfg['required_hats'][hatglob].split()):
426
local_profile[hat]['flags'] = 'complain'
428
created.append(localfile)
429
changed[localfile] = True
431
debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__()))
432
return {localfile: local_profile}
434
def delete_profile(local_prof):
435
"""Deletes the specified file from the disk and remove it from our list"""
436
profile_file = get_profile_filename(local_prof)
437
if os.path.isfile(profile_file):
438
os.remove(profile_file)
439
if aa.get(local_prof, False):
442
#prof_unload(local_prof)
444
def confirm_and_abort():
445
ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
447
aaui.UI_Info(_('Abandoning all changes.'))
453
def get_profile(prof_name):
455
distro = cfg['repository']['distro']
456
repo_url = cfg['repository']['url']
457
# local_profiles = []
458
profile_hash = hasher()
459
if repo_is_enabled():
460
aaui.UI_BusyStart(_('Connecting to repository...'))
461
status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name)
466
aaui.UI_Important(_('WARNING: Error fetching profiles from the repository'))
467
inactive_profile = get_inactive_profile(prof_name)
469
uname = 'Inactive local profile for %s' % prof_name
470
inactive_profile[prof_name][prof_name]['flags'] = 'complain'
471
inactive_profile[prof_name][prof_name].pop('filename')
472
profile_hash[uname]['username'] = uname
473
profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL'
474
profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name)
475
profile_hash[uname]['profile_data'] = inactive_profile
476
# If no profiles in repo and no inactive profiles
477
if not profile_hash.keys():
481
preferred_present = False
482
preferred_user = cfg['repository'].get('preferred_user', 'NOVELL')
484
for p in profile_hash.keys():
485
if profile_hash[p]['username'] == preferred_user:
486
preferred_present = True
488
tmp_list.append(profile_hash[p]['username'])
490
if preferred_present:
491
options.append(preferred_user)
495
q['headers'] = ['Profile', prof_name]
496
q['functions'] = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE',
497
'CMD_ABORT', 'CMD_FINISHED']
498
q['default'] = "CMD_VIEW_PROFILE"
499
q['options'] = options
503
while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans:
504
if ans == 'CMD_FINISHED':
508
ans, arg = aaui.UI_PromptUser(q)
509
p = profile_hash[options[arg]]
510
q['selected'] = options.index(options[arg])
511
if ans == 'CMD_VIEW_PROFILE':
512
if aaui.UI_mode == 'yast':
513
SendDataToYast({'type': 'dialogue-view-profile',
514
'user': options[arg],
515
'profile': p['profile'],
516
'profile_type': p['profile_type']
518
ypath, yarg = GetDataFromYast()
520
# pager = get_pager()
521
# proc = subprocess.Popen(pager, stdin=subprocess.PIPE)
522
# proc.communicate('Profile submitted by %s:\n\n%s\n\n' %
523
# (options[arg], p['profile']))
525
elif ans == 'CMD_USE_PROFILE':
526
if p['profile_type'] == 'INACTIVE_LOCAL':
527
profile_data = p['profile_data']
528
created.append(prof_name)
530
profile_data = parse_repo_profile(prof_name, repo_url, p)
533
def activate_repo_profiles(url, profiles, complain):
538
profile_data = parse_repo_profile(pname, url, p[1])
539
attach_profile_data(aa, profile_data)
542
fname = get_profile_filename(pname)
543
set_profile_flags(profile_dir + fname, 'complain')
544
aaui.UI_Info(_('Setting %s to complain mode.') % pname)
545
except Exception as e:
546
sys.stderr.write(_("Error activating profiles: %s") % e)
548
def autodep(bin_name, pname=''):
551
if not bin_name and pname.startswith('/'):
553
if not repo_cfg and not cfg['repository'].get('url', False):
554
repo_conf = apparmor.config.Config('shell', CONFDIR)
555
repo_cfg = repo_conf.read_config('repository.conf')
556
if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later':
557
UI_ask_to_enable_repo()
559
bin_full = find_executable(bin_name)
561
# bin_full = bin_name
562
#if not bin_full.startswith('/'):
564
# Return if exectuable path not found
568
bin_full = pname # for named profiles
571
read_inactive_profiles()
572
profile_data = get_profile(pname)
573
# Create a new profile if no existing profile
575
profile_data = create_new_profile(pname)
576
file = get_profile_filename(pname)
577
attach_profile_data(aa, profile_data)
578
attach_profile_data(original_aa, profile_data)
579
if os.path.isfile(profile_dir + '/tunables/global'):
580
if not filelist.get(file, False):
581
filelist[file] = hasher()
582
filelist[file]['include']['tunables/global'] = True
583
filelist[file]['profiles'][pname] = True
584
write_profile_ui_feedback(pname)
586
def get_profile_flags(filename, program):
588
# XXX If more than one profile in a file then second one is being ignored XXX
589
# Do we return flags for both or
591
with open_file_read(filename) as f_in:
593
if RE_PROFILE_START.search(line):
594
matches = RE_PROFILE_START.search(line).groups()
595
profile = matches[1] or matches[3]
597
if profile == program or program is None:
600
raise AppArmorException(_('%s contains no profile') % filename)
602
def change_profile_flags(filename, program, flag, set_flag):
603
old_flags = get_profile_flags(filename, program)
606
# Flags maybe white-space and/or , separated
607
old_flags = old_flags.split(',')
609
if not isinstance(old_flags, str):
611
newflags += i.split()
613
newflags = old_flags.split()
614
#newflags = [lambda x:x.strip(), oldflags]
617
if flag not in newflags:
618
newflags.append(flag)
621
newflags.remove(flag)
623
newflags = ','.join(newflags)
625
set_profile_flags(filename, program, newflags)
627
def set_profile_flags(prof_filename, program, newflags):
628
"""Reads the old profile file and updates the flags accordingly"""
629
regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$')
630
regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$')
631
if os.path.isfile(prof_filename):
632
with open_file_read(prof_filename) as f_in:
633
temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir)
634
shutil.copymode(prof_filename, temp_file.name)
635
with open_file_write(temp_file.name) as f_out:
639
comment = '#' + line.split('#', 1)[1].rstrip()
640
match = regex_bin_flag.search(line)
641
if not line.strip() or line.strip().startswith('#'):
644
matches = match.groups()
647
flag = matches[6] or 'flags='
649
if binary == program or program is None:
651
line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment)
653
line = '%s%s {%s\n' % (space, binary, comment)
655
match = regex_hat_flag.search(line)
657
hat, flags = match.groups()[:2]
659
line = '%s flags=(%s) {%s\n' % (hat, newflags, comment)
661
line = '%s {%s\n' % (hat, comment)
663
os.rename(temp_file.name, prof_filename)
665
def profile_exists(program):
666
"""Returns True if profile exists, False otherwise"""
667
# Check cache of profiles
669
if existing_profiles.get(program, False):
671
# Check the disk for profile
672
prof_path = get_profile_filename(program)
674
if os.path.isfile(prof_path):
675
# Add to cache of profile
676
existing_profiles[program] = prof_path
681
user, passw = get_repo_user_pass()
682
if not user or not passw:
685
changed_profiles = []
687
serialize_opts = hasher()
688
status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'],
689
cfg['repository']['distro'], user)
692
ret = 'UNKNOWN ERROR'
693
aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret)
695
users_repo_profiles = ret
696
serialize_opts['NO_FLAGS'] = True
697
for prof in sorted(aa.keys()):
698
if is_repo_profile([aa[prof][prof]]):
699
repo_profiles.append(prof)
701
p_local = serialize_profile(aa[prof], prof, serialize_opts)
702
if not users_repo_profiles.get(prof, False):
703
new_profiles.append(prof)
704
new_profiles.append(p_local)
705
new_profiles.append('')
707
p_repo = users_repo_profiles[prof]['profile']
708
if p_local != p_repo:
709
changed_profiles.append(prof)
710
changed_profiles.append(p_local)
711
changed_profiles.append(p_repo)
713
for prof in repo_profiles:
714
p_local = serialize_profile(aa[prof], prof, serialize_opts)
715
if not users_repo_profiles.get(prof, False):
716
new_profiles.append(prof)
717
new_profiles.append(p_local)
718
new_profiles.append('')
721
if aa[prof][prof]['repo']['user'] == user:
722
p_repo = users_repo_profiles[prof]['profile']
724
status_ok, ret = fetch_profile_by_id(cfg['repository']['url'],
725
aa[prof][prof]['repo']['id'])
727
p_repo = ret['profile']
730
ret = 'UNKNOWN ERROR'
731
aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret)
733
if p_repo != p_local:
734
changed_profiles.append(prof)
735
changed_profiles.append(p_local)
736
changed_profiles.append(p_repo)
738
submit_changed_profiles(changed_profiles)
740
submit_created_profiles(new_profiles)
742
def fetch_profile_by_id(url, id):
746
def fetch_profiles_by_name(url, distro, user):
750
def fetch_profiles_by_user(url, distro, user):
754
def submit_created_profiles(new_profiles):
755
#url = cfg['repository']['url']
757
if aaui.UI_mode == 'yast':
758
title = 'New Profiles'
759
message = 'Please select the newly created profiles that you would like to store in the repository'
760
yast_select_and_upload_profiles(title, message, new_profiles)
762
title = 'Submit newly created profiles to the repository'
763
message = 'Would you like to upload newly created profiles?'
764
console_select_and_upload_profiles(title, message, new_profiles)
766
def submit_changed_profiles(changed_profiles):
767
#url = cfg['repository']['url']
769
if aaui.UI_mode == 'yast':
770
title = 'Changed Profiles'
771
message = 'Please select which of the changed profiles would you like to upload to the repository'
772
yast_select_and_upload_profiles(title, message, changed_profiles)
774
title = 'Submit changed profiles to the repository'
775
message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?'
776
console_select_and_upload_profiles(title, message, changed_profiles)
778
def yast_select_and_upload_profiles(title, message, profiles_up):
779
url = cfg['repository']['url']
780
profile_changes = hasher()
781
profs = profiles_up[:]
783
profile_changes[p[0]] = get_profile_diff(p[2], p[1])
784
SendDataToYast({'type': 'dialog-select-profiles',
786
'explanation': message,
787
'default_select': 'false',
788
'disable_ask_upload': 'true',
789
'profiles': profile_changes
791
ypath, yarg = GetDataFromYast()
792
selected_profiles = []
795
single_changelog = False
796
if yarg['STATUS'] == 'cancel':
799
selected_profiles = yarg['PROFILES']
800
changelogs = yarg['CHANGELOG']
801
if changelogs.get('SINGLE_CHANGELOG', False):
802
changelog = changelogs['SINGLE_CHANGELOG']
803
single_changelog = True
804
user, passw = get_repo_user_pass()
805
for p in selected_profiles:
806
profile_string = serialize_profile(aa[p], p)
807
if not single_changelog:
808
changelog = changelogs[p]
809
status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'],
810
p, profile_string, changelog)
813
newid = newprofile['id']
814
set_repo_info(aa[p][p], url, user, newid)
815
write_profile_ui_feedback(p)
818
ret = 'UNKNOWN ERROR'
819
aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret))
820
aaui.UI_Info(_('Uploaded changes to repository.'))
821
if yarg.get('NEVER_ASK_AGAIN'):
822
unselected_profiles = []
824
if p[0] not in selected_profiles:
825
unselected_profiles.append(p[0])
826
set_profiles_local_only(unselected_profiles)
828
def upload_profile(url, user, passw, distro, p, profile_string, changelog):
832
def console_select_and_upload_profiles(title, message, profiles_up):
833
url = cfg['repository']['url']
834
profs = profiles_up[:]
837
q['headers'] = ['Repository', url]
838
q['explanation'] = message
839
q['functions'] = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER',
840
'CMD_ASK_NEVER', 'CMD_ABORT']
841
q['default'] = 'CMD_VIEW_CHANGES'
842
q['options'] = [i[0] for i in profs]
845
while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans:
846
ans, arg = aaui.UI_PromptUser(q)
847
if ans == 'CMD_VIEW_CHANGES':
848
display_changes(profs[arg][2], profs[arg][1])
849
if ans == 'CMD_NEVER_ASK':
850
set_profiles_local_only([i[0] for i in profs])
851
elif ans == 'CMD_UPLOAD_CHANGES':
852
changelog = aaui.UI_GetString(_('Changelog Entry: '), '')
853
user, passw = get_repo_user_pass()
857
prof_string = p_data[1]
858
status_ok, ret = upload_profile(url, user, passw,
859
cfg['repository']['distro'],
860
prof, prof_string, changelog)
863
newid = newprof['id']
864
set_repo_info(aa[prof][prof], url, user, newid)
865
write_profile_ui_feedback(prof)
866
aaui.UI_Info('Uploaded %s to repository' % prof)
869
ret = 'UNKNOWN ERROR'
870
aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret))
872
aaui.UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.'))
874
def set_profiles_local_only(profs):
876
aa[profs][profs]['repo']['neversubmit'] = True
877
write_profile_ui_feedback(profs)
880
def build_x_functions(default, options, exec_toggle):
882
fallback_toggle = False
885
ret_list.append('CMD_ix')
887
ret_list.append('CMD_pix')
888
fallback_toggle = True
890
ret_list.append('CMD_cix')
891
fallback_toggle = True
893
ret_list.append('CMD_nix')
894
fallback_toggle = True
896
ret_list.append('CMD_EXEC_IX_OFF')
898
ret_list.append('CMD_ux')
902
ret_list.append('CMD_ix')
904
ret_list.append('CMD_cx')
905
fallback_toggle = True
907
ret_list.append('CMD_px')
908
fallback_toggle = True
910
ret_list.append('CMD_nx')
911
fallback_toggle = True
913
ret_list.append('CMD_ux')
916
ret_list.append('CMD_EXEC_IX_ON')
918
ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
921
def handle_children(profile, hat, root):
937
regex_nullcomplain = re.compile('^null(-complain)*-profile$')
939
for entry in entries:
940
if type(entry[0]) != str:
941
handle_children(profile, hat, entry)
945
# If type is fork then we (should) have pid, profile and hat
946
pid, p, h = entry[:3]
947
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
951
profile_changes[pid] = profile + '//' + hat
953
profile_changes[pid] = profile
954
elif typ == 'unknown_hat':
955
# If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry
956
pid, p, h, aamode, uhat = entry[:5]
957
if not regex_nullcomplain.search(p):
959
if aa[profile].get(uhat, False):
962
new_p = update_repo_profile(aa[profile][profile])
963
if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False):
968
for hatglob in cfg.options('defaulthat'):
969
if re.search(hatglob, profile):
970
default_hat = cfg['defaulthat'][hatglob]
973
context = context + ' -> ^%s' % uhat
974
ans = transitions.get(context, 'XXXINVALIDXXX')
976
while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']:
979
q['headers'] += [_('Profile'), profile]
982
q['headers'] += [_('Default Hat'), default_hat]
984
q['headers'] += [_('Requested Hat'), uhat]
987
q['functions'].append('CMD_ADDHAT')
989
q['functions'].append('CMD_USEDEFAULT')
990
q['functions'] += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
992
q['default'] = 'CMD_DENY'
993
if aamode == 'PERMITTING':
994
q['default'] = 'CMD_ADDHAT'
998
ans = aaui.UI_PromptUser(q)
1000
if ans == 'CMD_FINISHED':
1004
transitions[context] = ans
1006
if ans == 'CMD_ADDHAT':
1008
aa[profile][hat]['flags'] = aa[profile][profile]['flags']
1009
elif ans == 'CMD_USEDEFAULT':
1011
elif ans == 'CMD_DENY':
1012
# As unknown hat is denied no entry for it should be made
1015
elif typ == 'capability':
1016
# If capability then we (should) have pid, profile, hat, program, mode, capability
1017
pid, p, h, prog, aamode, capability = entry[:6]
1018
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
1021
if not profile or not hat:
1023
prelog[aamode][profile][hat]['capability'][capability] = True
1025
elif typ == 'path' or typ == 'exec':
1026
# If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name
1027
pid, p, h, prog, aamode, mode, detail, to_name = entry[:8]
1030
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
1033
if not profile or not hat or not detail:
1036
domainchange = 'nochange'
1038
domainchange = 'change'
1040
# Escape special characters
1041
detail = detail.replace('[', '\[')
1042
detail = detail.replace(']', '\]')
1043
detail = detail.replace('+', '\+')
1044
detail = detail.replace('*', '\*')
1045
detail = detail.replace('{', '\{')
1046
detail = detail.replace('}', '\}')
1048
# Give Execute dialog if x access requested for something that's not a directory
1049
# For directories force an 'ix' Path dialog
1051
exec_target = detail
1053
if mode & str_to_mode('x'):
1054
if os.path.isdir(exec_target):
1055
mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE
1056
mode = mode | str_to_mode('ix')
1060
if mode & apparmor.aamode.AA_MAY_LINK:
1061
regex_link = re.compile('^from (.+) to (.+)$')
1062
match = regex_link.search(detail)
1064
path = match.groups()[0]
1065
target = match.groups()[1]
1067
frommode = str_to_mode('lr')
1068
if prelog[aamode][profile][hat]['path'].get(path, False):
1069
frommode |= prelog[aamode][profile][hat]['path'][path]
1070
prelog[aamode][profile][hat]['path'][path] = frommode
1072
tomode = str_to_mode('lr')
1073
if prelog[aamode][profile][hat]['path'].get(target, False):
1074
tomode |= prelog[aamode][profile][hat]['path'][target]
1075
prelog[aamode][profile][hat]['path'][target] = tomode
1081
if prelog[aamode][profile][hat]['path'].get(path, False):
1082
mode |= prelog[aamode][profile][hat]['path'][path]
1083
prelog[aamode][profile][hat]['path'][path] = mode
1086
if profile_known_exec(aa[profile][hat], 'exec', exec_target):
1089
p = update_repo_profile(aa[profile][profile])
1091
if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', to_name):
1094
if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', exec_target):
1097
context_new = profile
1099
context_new = context_new + '^%s' % hat
1100
context_new = context_new + ' -> %s' % exec_target
1102
# ans_new = transitions.get(context_new, '') # XXX ans meant here?
1103
combinedmode = set()
1104
combinedaudit = set()
1105
## Check return Value Consistency
1106
# Check if path matches any existing regexps in profile
1107
cm, am, m = rematchfrag(aa[profile][hat], 'allow', exec_target)
1113
if combinedmode & str_to_mode('x'):
1116
if aa[profile][hat]['allow']['path'].get(entr, False):
1117
nt_name = aa[profile][hat]
1119
if to_name and to_name != nt_name:
1123
## Check return value consistency
1124
# Check if the includes from profile match
1125
cm, am, m = match_prof_incs_to_path(aa[profile][hat], 'allow', exec_target)
1130
if combinedmode & str_to_mode('x'):
1133
if aa[profile][hat]['allow']['path'][entry]['to']:
1134
nt_name = aa[profile][hat]['allow']['path'][entry]['to']
1136
if to_name and to_name != nt_name:
1141
# nx is not used in profiles but in log files.
1142
# Log parsing methods will convert it to its profile form
1143
# nx is internally cx/px/cix/pix + to_name
1145
if contains(combinedmode, 'pix'):
1150
exec_mode = str_to_mode('pixr')
1151
elif contains(combinedmode, 'cix'):
1156
exec_mode = str_to_mode('cixr')
1157
elif contains(combinedmode, 'Pix'):
1159
ans = 'CMD_nix_safe'
1161
ans = 'CMD_pix_safe'
1162
exec_mode = str_to_mode('Pixr')
1163
elif contains(combinedmode, 'Cix'):
1165
ans = 'CMD_nix_safe'
1167
ans = 'CMD_cix_safe'
1168
exec_mode = str_to_mode('Cixr')
1169
elif contains(combinedmode, 'ix'):
1171
exec_mode = str_to_mode('ixr')
1172
elif contains(combinedmode, 'px'):
1177
exec_mode = str_to_mode('px')
1178
elif contains(combinedmode, 'cx'):
1183
exec_mode = str_to_mode('cx')
1184
elif contains(combinedmode, 'ux'):
1186
exec_mode = str_to_mode('ux')
1187
elif contains(combinedmode, 'Px'):
1192
exec_mode = str_to_mode('Px')
1193
elif contains(combinedmode, 'Cx'):
1198
exec_mode = str_to_mode('Cx')
1199
elif contains(combinedmode, 'Ux'):
1201
exec_mode = str_to_mode('Ux')
1203
options = cfg['qualifiers'].get(exec_target, 'ipcnu')
1205
fatal_error(_('%s has transition name but not transition mode') % entry)
1207
### If profiled program executes itself only 'ix' option
1208
##if exec_target == profile:
1211
# Don't allow hats to cx?
1212
options.replace('c', '')
1213
# Add deny to options
1215
# Define the default option
1217
if 'p' in options and os.path.exists(get_profile_filename(exec_target)):
1219
sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target))
1220
elif 'i' in options:
1222
elif 'c' in options:
1224
elif 'n' in options:
1230
parent_uses_ld_xxx = check_for_LD_XXX(profile)
1232
sev_db.unload_variables()
1233
sev_db.load_variables(profile)
1234
severity = sev_db.rank(exec_target, 'x')
1236
# Prompt portion starts
1239
q['headers'] += [_('Profile'), combine_name(profile, hat)]
1240
if prog and prog != 'HINT':
1241
q['headers'] += [_('Program'), prog]
1243
# to_name should not exist here since, transitioning is already handeled
1244
q['headers'] += [_('Execute'), exec_target]
1245
q['headers'] += [_('Severity'), severity]
1248
# prompt = '\n%s\n' % context_new # XXX
1250
q['functions'] += build_x_functions(default, options, exec_toggle)
1252
# options = '|'.join(options)
1254
regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$')
1257
while not regex_options.search(ans):
1258
ans = aaui.UI_PromptUser(q)[0].strip()
1259
if ans.startswith('CMD_EXEC_IX_'):
1260
exec_toggle = not exec_toggle
1262
q['functions'] += build_x_functions(default, options, exec_toggle)
1266
if ans == 'CMD_FINISHED':
1270
if ans == 'CMD_nx' or ans == 'CMD_nix':
1274
ynans = aaui.UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n')
1286
to_name = aaui.UI_GetString(_('Enter profile name to transition to: '), arg)
1288
regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)')
1290
exec_mode = str_to_mode('ix')
1291
elif regex_optmode.search(ans):
1292
match = regex_optmode.search(ans).groups()[0]
1293
exec_mode = str_to_mode(match)
1295
px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.")
1296
if parent_uses_ld_xxx:
1297
px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.")
1299
ynans = aaui.UI_YesNo(px_msg, px_default)
1301
# Disable the unsafe mode
1302
exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE))
1303
elif ans == 'CMD_ux':
1304
exec_mode = str_to_mode('ux')
1305
ynans = aaui.UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n')
1307
ynans = aaui.UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y')
1309
# Disable the unsafe mode
1310
exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE))
1313
transitions[context_new] = ans
1315
regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)')
1316
if regex_options.search(ans):
1317
# For inherit we need r
1318
if exec_mode & str_to_mode('i'):
1319
exec_mode |= str_to_mode('r')
1321
if ans == 'CMD_DENY':
1322
aa[profile][hat]['deny']['path'][exec_target]['mode'] = aa[profile][hat]['deny']['path'][exec_target].get('mode', str_to_mode('x')) | str_to_mode('x')
1323
aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', set())
1324
changed[profile] = True
1325
# Skip remaining events if they ask to deny exec
1326
if domainchange == 'change':
1329
if ans != 'CMD_DENY':
1330
prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode
1332
log_dict['PERMITTING'][profile] = hasher()
1334
aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode)
1336
aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', set())
1339
aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name
1341
changed[profile] = True
1343
if exec_mode & str_to_mode('i'):
1344
#if 'perl' in exec_target:
1345
# aa[profile][hat]['include']['abstractions/perl'] = True
1346
#elif '/bin/bash' in exec_target or '/bin/sh' in exec_target:
1347
# aa[profile][hat]['include']['abstractions/bash'] = True
1348
hashbang = head(exec_target)
1349
if hashbang.startswith('#!'):
1350
interpreter = hashbang[2:].strip()
1351
interpreter_path = get_full_path(interpreter)
1352
interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path)
1354
aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix')
1356
aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', set())
1358
if interpreter == 'perl':
1359
aa[profile][hat]['include']['abstractions/perl'] = True
1360
elif interpreter in ['bash', 'dash', 'sh']:
1361
aa[profile][hat]['include']['abstractions/bash'] = True
1363
# Update tracking info based on kind of change
1367
profile_changes[pid] = '%s//%s' % (profile, hat)
1369
profile_changes[pid] = '%s//' % profile
1370
elif re.search('^CMD_(px|nx|pix|nix)', ans):
1372
exec_target = to_name
1373
if aamode == 'PERMITTING':
1374
if domainchange == 'change':
1375
profile = exec_target
1377
profile_changes[pid] = '%s' % profile
1379
# Check profile exists for px
1380
if not os.path.exists(get_profile_filename(exec_target)):
1382
if exec_mode & str_to_mode('i'):
1383
ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
1385
helpers[exec_target] = 'enforce'
1387
autodep('', exec_target)
1389
autodep(exec_target, '')
1390
reload_base(exec_target)
1391
elif ans.startswith('CMD_cx') or ans.startswith('CMD_cix'):
1393
exec_target = to_name
1394
if aamode == 'PERMITTING':
1395
if domainchange == 'change':
1396
profile_changes[pid] = '%s//%s' % (profile, exec_target)
1398
if not aa[profile].get(exec_target, False):
1400
if exec_mode & str_to_mode('i'):
1401
ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
1404
aa[profile][hat]['declared'] = False
1405
aa[profile][hat]['profile'] = True
1408
aa[profile][hat]['flags'] = aa[profile][profile]['flags']
1410
stub_profile = create_new_profile(hat)
1412
aa[profile][hat]['flags'] = 'complain'
1414
aa[profile][hat]['allow']['path'] = hasher()
1415
if stub_profile[hat][hat]['allow'].get('path', False):
1416
aa[profile][hat]['allow']['path'] = stub_profile[hat][hat]['allow']['path']
1418
aa[profile][hat]['include'] = hasher()
1419
if stub_profile[hat][hat].get('include', False):
1420
aa[profile][hat]['include'] = stub_profile[hat][hat]['include']
1422
aa[profile][hat]['allow']['netdomain'] = hasher()
1424
file_name = aa[profile][profile]['filename']
1425
filelist[file_name]['profiles'][profile][hat] = True
1427
elif ans.startswith('CMD_ux'):
1428
profile_changes[pid] = 'unconfined'
1429
if domainchange == 'change':
1432
elif typ == 'netdomain':
1433
# If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol
1434
pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8]
1436
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
1439
if not hat or not profile:
1441
if family and sock_type:
1442
prelog[aamode][profile][hat]['netdomain'][family][sock_type] = True
1446
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
1447
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
1448
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
1450
##### Repo related functions
1452
def UI_SelectUpdatedRepoProfile(profile, p):
1456
def UI_repo_signup():
1460
def UI_ask_to_enable_repo():
1464
def UI_ask_to_upload_profiles():
1468
def UI_ask_mode_toggles(audit_toggle, owner_toggle, oldmode):
1470
return (audit_toggle, owner_toggle)
1472
def parse_repo_profile(fqdbin, repo_url, profile):
1476
def set_repo_info(profile_data, repo_url, username, iden):
1480
def is_repo_profile(profile_data):
1484
def get_repo_user_pass():
1487
def get_preferred_user(repo_url):
1490
def repo_is_enabled():
1494
def update_repo_profile(profile):
1498
def order_globs(globs, path):
1499
"""Returns the globs in sorted order, more specific behind"""
1501
# ATM its lexicographic, should be done to allow better matches later
1502
return sorted(globs)
1504
def ask_the_questions():
1507
for aamode in sorted(log_dict.keys()):
1508
# Describe the type of changes
1509
if aamode == 'PERMITTING':
1510
aaui.UI_Info(_('Complain-mode changes:'))
1511
elif aamode == 'REJECTING':
1512
aaui.UI_Info(_('Enforce-mode changes:'))
1515
fatal_error(_('Invalid mode found: %s') % aamode)
1517
for profile in sorted(log_dict[aamode].keys()):
1518
# Update the repo profiles
1519
p = update_repo_profile(aa[profile][profile])
1521
UI_SelectUpdatedRepoProfile(profile, p)
1524
# Sorted list of hats with the profile name coming first
1525
hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())))
1526
if log_dict[aamode][profile].get(profile, False):
1527
hats = [profile] + hats
1530
for capability in sorted(log_dict[aamode][profile][hat]['capability'].keys()):
1531
# skip if capability already in profile
1532
if profile_known_capability(aa[profile][hat], capability):
1534
# Load variables? Don't think so.
1535
severity = sev_db.rank('CAP_%s' % capability)
1538
newincludes = match_cap_includes(aa[profile][hat], capability)
1542
options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes))))
1545
options.append('capability %s' % capability)
1546
q['options'] = [options]
1547
q['selected'] = default_option - 1
1549
q['headers'] = [_('Profile'), combine_name(profile, hat)]
1550
q['headers'] += [_('Capability'), capability]
1551
q['headers'] += [_('Severity'), severity]
1555
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
1556
'CMD_ABORT', 'CMD_FINISHED']
1558
# In complain mode: events default to allow
1559
# In enforce mode: events default to deny
1560
q['default'] = 'CMD_DENY'
1561
if aamode == 'PERMITTING':
1562
q['default'] = 'CMD_ALLOW'
1568
ans, selected = aaui.UI_PromptUser(q)
1570
if ans == 'CMD_FINISHED':
1574
# Ignore the log entry
1575
if ans == 'CMD_IGNORE_ENTRY':
1579
if ans == 'CMD_AUDIT':
1580
audit_toggle = not audit_toggle
1583
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_OFF',
1584
'CMD_ABORT', 'CMD_FINISHED']
1587
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
1588
'CMD_ABORT', 'CMD_FINISHED', ]
1590
q['headers'] = [_('Profile'), combine_name(profile, hat),
1591
_('Capability'), audit + capability,
1592
_('Severity'), severity]
1594
if ans == 'CMD_ALLOW':
1597
selection = options[selected]
1598
match = re_match_include(selection)
1601
inc = match # .groups()[0]
1602
deleted = delete_duplicates(aa[profile][hat], inc)
1603
aa[profile][hat]['include'][inc] = True
1605
aaui.UI_Info(_('Adding %s to profile.') % selection)
1607
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
1609
aa[profile][hat]['allow']['capability'][capability]['set'] = True
1610
aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle
1612
changed[profile] = True
1614
aaui.UI_Info(_('Adding capability %s to profile.') % capability)
1617
elif ans == 'CMD_DENY':
1618
aa[profile][hat]['deny']['capability'][capability]['set'] = True
1619
changed[profile] = True
1621
aaui.UI_Info(_('Denying capability %s to profile.') % capability)
1626
# Process all the path entries.
1627
for path in sorted(log_dict[aamode][profile][hat]['path'].keys()):
1628
mode = log_dict[aamode][profile][hat]['path'][path]
1629
# Lookup modes from profile
1635
fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path)
1639
allow_audit |= famode
1641
cm, cam, m = rematchfrag(aa[profile][hat], 'deny', path)
1647
imode, iamode, im = match_prof_incs_to_path(aa[profile][hat], 'allow', path)
1651
allow_audit |= iamode
1653
cm, cam, m = match_prof_incs_to_path(aa[profile][hat], 'deny', path)
1659
if deny_mode & apparmor.aamode.AA_MAY_EXEC:
1660
deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE
1662
# Mask off the denied modes
1663
mode = mode - deny_mode
1665
# If we get an exec request from some kindof event that generates 'PERMITTING X'
1666
# check if its already in allow_mode
1667
# if not add ix permission
1668
if mode & apparmor.aamode.AA_MAY_EXEC:
1669
# Remove all type access permission
1670
mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE
1671
if not allow_mode & apparmor.aamode.AA_MAY_EXEC:
1672
mode |= str_to_mode('ix')
1674
# m is not implied by ix
1676
### If we get an mmap request, check if we already have it in allow_mode
1677
##if mode & AA_EXEC_MMAP:
1678
## # ix implies m, so we don't need to add m if ix is present
1679
## if contains(allow_mode, 'ix'):
1680
## mode = mode - AA_EXEC_MMAP
1693
if not mode_contains(allow_mode, mode):
1697
include_valid = False
1699
for incname in include.keys():
1700
include_valid = False
1701
# If already present skip
1702
if aa[profile][hat][incname]:
1704
if incname.startswith(profile_dir):
1705
incname = incname.replace(profile_dir + '/', '', 1)
1707
include_valid = valid_include('', incname)
1709
if not include_valid:
1712
cm, am, m = match_include_to_path(incname, 'allow', path)
1714
if cm and mode_contains(cm, mode):
1715
dm = match_include_to_path(incname, 'deny', path)[0]
1716
# If the mode is denied
1718
if not list(filter(lambda s: '/**' == s, m)):
1719
newincludes.append(incname)
1720
# Add new includes to the options
1722
options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes))))
1723
# We should have literal the path in options list too
1724
options.append(path)
1725
# Add any the globs matching path from logprof
1726
globs = glob_common(path)
1729
# Add any user entered matching globs
1730
for user_glob in user_globs:
1731
if matchliteral(user_glob, path):
1732
matches.append(user_glob)
1734
matches = list(set(matches))
1736
matches.remove(path)
1738
options += order_globs(matches, path)
1739
default_option = len(options)
1741
sev_db.unload_variables()
1742
sev_db.load_variables(get_profile_filename(profile))
1743
severity = sev_db.rank(path, mode_to_str(mode))
1744
sev_db.unload_variables()
1748
if cfg['settings']['default_owner_prompt']:
1749
owner_toggle = cfg['settings']['default_owner_prompt']
1753
q['headers'] = [_('Profile'), combine_name(profile, hat),
1761
if owner_toggle == 0:
1762
prompt_mode = flatten_mode(mode)
1763
tail = ' ' + _('(owner permissions off)')
1764
elif owner_toggle == 1:
1766
elif owner_toggle == 2:
1767
prompt_mode = allow_mode | owner_flatten_mode(mode - allow_mode)
1768
tail = ' ' + _('(force new perms to owner)')
1770
prompt_mode = owner_flatten_mode(mode)
1771
tail = ' ' + _('(force all rule perms to owner)')
1773
if audit_toggle == 1:
1774
s = mode_to_str_user(allow_mode)
1777
s += 'audit ' + mode_to_str_user(prompt_mode - allow_mode) + tail
1778
elif audit_toggle == 2:
1779
s = 'audit ' + mode_to_str_user(prompt_mode) + tail
1781
s = mode_to_str_user(prompt_mode) + tail
1783
q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode),
1792
if owner_toggle == 0:
1793
prompt_mode = flatten_mode(mode)
1794
tail = ' ' + _('(owner permissions off)')
1795
elif owner_toggle == 1:
1798
prompt_mode = owner_flatten_mode(mode)
1799
tail = ' ' + _('(force perms to owner)')
1801
s = mode_to_str_user(prompt_mode)
1802
q['headers'] += [_('Mode'), s]
1804
q['headers'] += [_('Severity'), severity]
1805
q['options'] = options
1806
q['selected'] = default_option - 1
1807
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB',
1808
'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT',
1809
'CMD_FINISHED', 'CMD_OTHER']
1810
q['default'] = 'CMD_DENY'
1811
if aamode == 'PERMITTING':
1812
q['default'] = 'CMD_ALLOW'
1816
ans, selected = aaui.UI_PromptUser(q)
1818
if ans == 'CMD_FINISHED':
1822
if ans == 'CMD_IGNORE_ENTRY':
1826
if ans == 'CMD_OTHER':
1827
audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode)
1828
elif ans == 'CMD_USER_TOGGLE':
1830
if not allow_mode and owner_toggle == 2:
1832
if owner_toggle > 3:
1834
elif ans == 'CMD_ALLOW':
1835
path = options[selected]
1837
match = re_match_include(path) # .search('^#include\s+<(.+)>$', path)
1839
inc = match # .groups()[0]
1841
deleted = delete_duplicates(aa[profile][hat], inc)
1842
aa[profile][hat]['include'][inc] = True
1843
changed[profile] = True
1844
aaui.UI_Info(_('Adding %s to profile.') % path)
1846
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
1849
if aa[profile][hat]['allow']['path'][path].get('mode', False):
1850
mode |= aa[profile][hat]['allow']['path'][path]['mode']
1852
for entry in aa[profile][hat]['allow']['path'].keys():
1856
if matchregexp(path, entry):
1857
if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']):
1858
deleted.append(entry)
1859
for entry in deleted:
1860
aa[profile][hat]['allow']['path'].pop(entry)
1861
deleted = len(deleted)
1863
if owner_toggle == 0:
1864
mode = flatten_mode(mode)
1865
#elif owner_toggle == 1:
1867
elif owner_toggle == 2:
1868
mode = allow_mode | owner_flatten_mode(mode - allow_mode)
1869
elif owner_toggle == 3:
1870
mode = owner_flatten_mode(mode)
1872
aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode
1875
if audit_toggle == 1:
1876
tmpmode = mode - allow_mode
1877
elif audit_toggle == 2:
1880
aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode
1882
changed[profile] = True
1884
aaui.UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode)))
1886
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
1888
elif ans == 'CMD_DENY':
1889
path = options[selected].strip()
1891
aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode)
1893
aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', set())
1895
changed[profile] = True
1899
elif ans == 'CMD_NEW':
1900
arg = options[selected]
1901
if not re_match_include(arg):
1902
ans = aaui.UI_GetString(_('Enter new path: '), arg)
1904
if not matchliteral(ans, path):
1905
ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path, ans)
1906
key = aaui.UI_YesNo(ynprompt, 'n')
1910
user_globs.append(ans)
1912
default_option = len(options)
1914
elif ans == 'CMD_GLOB':
1915
newpath = options[selected].strip()
1916
if not re_match_include(newpath):
1917
newpath = glob_path(newpath)
1919
if newpath not in options:
1920
options.append(newpath)
1921
default_option = len(options)
1923
default_option = options.index(newpath) + 1
1925
elif ans == 'CMD_GLOBEXT':
1926
newpath = options[selected].strip()
1927
if not re_match_include(newpath):
1928
newpath = glob_path_withext(newpath)
1930
if newpath not in options:
1931
options.append(newpath)
1932
default_option = len(options)
1934
default_option = options.index(newpath) + 1
1936
elif re.search('\d', ans):
1937
default_option = ans
1940
for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()):
1941
# severity handling for net toggles goes here
1942
for sock_type in sorted(log_dict[profile][profile][hat]['netdomain'][family].keys()):
1943
if profile_known_network(aa[profile][hat], family, sock_type):
1947
newincludes = match_net_includes(aa[profile][hat], family, sock_type)
1950
options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes))))
1952
options.append('network %s %s' % (family, sock_type))
1953
q['options'] = options
1954
q['selected'] = default_option - 1
1956
q['headers'] = [_('Profile'), combine_name(profile, hat)]
1957
q['headers'] += [_('Network Family'), family]
1958
q['headers'] += [_('Socket Type'), sock_type]
1961
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
1962
'CMD_ABORT', 'CMD_FINISHED']
1963
q['default'] = 'CMD_DENY'
1965
if aamode == 'PERMITTING':
1966
q['default'] = 'CMD_ALLOW'
1972
ans, selected = aaui.UI_PromptUser(q)
1974
if ans == 'CMD_FINISHED':
1978
if ans == 'CMD_IGNORE_ENTRY':
1982
if ans.startswith('CMD_AUDIT'):
1983
audit_toggle = not audit_toggle
1987
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF',
1988
'CMD_ABORT', 'CMD_FINISHED']
1990
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW',
1991
'CMD_ABORT', 'CMD_FINISHED']
1992
q['headers'] = [_('Profile'), combine_name(profile, hat)]
1993
q['headers'] += [_('Network Family'), audit + family]
1994
q['headers'] += [_('Socket Type'), sock_type]
1996
elif ans == 'CMD_ALLOW':
1997
selection = options[selected]
1999
if re_match_include(selection): # re.search('#include\s+<.+>$', selection):
2000
inc = re_match_include(selection) # re.search('#include\s+<(.+)>$', selection).groups()[0]
2002
deleted = delete_duplicates(aa[profile][hat], inc)
2004
aa[profile][hat]['include'][inc] = True
2006
changed[profile] = True
2008
aaui.UI_Info(_('Adding %s to profile') % selection)
2010
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
2013
aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle
2014
aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True
2016
changed[profile] = True
2018
aaui.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type))
2020
elif ans == 'CMD_DENY':
2022
aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True
2023
changed[profile] = True
2024
aaui.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type))
2029
def glob_path(newpath):
2030
"""Glob the given file path"""
2031
if newpath[-1] == '/':
2032
if newpath[-4:] == '/**/' or newpath[-3:] == '/*/':
2033
# /foo/**/ and /foo/*/ => /**/
2034
newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) # re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath)
2035
elif re.search('/[^/]+\*\*[^/]*/$', newpath):
2036
# /foo**/ and /foo**bar/ => /**/
2037
newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath)
2038
elif re.search('/\*\*[^/]+/$', newpath):
2040
newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath)
2042
newpath = re.sub('/[^/]+/$', '/*/', newpath)
2044
if newpath[-3:] == '/**' or newpath[-2:] == '/*':
2045
# /foo/** and /foo/* => /**
2046
newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath)
2047
elif re.search('/[^/]*\*\*[^/]+$', newpath):
2048
# /**foo and /foor**bar => /**
2049
newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath)
2050
elif re.search('/[^/]+\*\*$', newpath):
2052
newpath = re.sub('/[^/]+\*\*$', '/**', newpath)
2054
newpath = re.sub('/[^/]+$', '/*', newpath)
2057
def glob_path_withext(newpath):
2058
"""Glob given file path with extension"""
2059
# match /**.ext and /*.ext
2060
match = re.search('/\*{1,2}(\.[^/]+)$', newpath)
2062
# /foo/**.ext and /foo/*.ext => /**.ext
2063
newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], newpath)
2064
elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath):
2065
# /foo**.ext and /foo**bar.ext => /**.ext
2066
match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath)
2067
newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], newpath)
2068
elif re.search('/\*\*[^/]+\.[^/]+$', newpath):
2069
# /**foo.ext => /**.ext
2070
match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath)
2071
newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], newpath)
2073
match = re.search('(\.[^/]+)$', newpath)
2075
newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], newpath)
2078
def delete_net_duplicates(netrules, incnetrules):
2080
hasher_obj = hasher()
2081
copy_netrules = deepcopy(netrules)
2082
if incnetrules and netrules:
2084
# Delete matching rules from abstractions
2085
if incnetrules.get('all', False):
2087
for fam in copy_netrules['rule'].keys():
2088
if incnetglob or (type(incnetrules['rule'][fam]) != type(hasher_obj) and incnetrules['rule'][fam]):
2089
if type(netrules['rule'][fam]) == type(hasher_obj):
2090
deleted += len(netrules['rule'][fam].keys())
2093
netrules['rule'].pop(fam)
2094
elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]:
2097
for socket_type in copy_netrules['rule'][fam].keys():
2098
if incnetrules['rule'][fam].get(socket_type, False):
2099
netrules['rule'][fam].pop(socket_type)
2103
def delete_cap_duplicates(profilecaps, inccaps):
2105
if profilecaps and inccaps:
2106
for capname in profilecaps.keys():
2107
if inccaps[capname].get('set', False) == 1:
2108
deleted.append(capname)
2109
for capname in deleted:
2110
profilecaps.pop(capname)
2114
def delete_path_duplicates(profile, incname, allow):
2116
for entry in profile[allow]['path'].keys():
2117
if entry == '#include <%s>' % incname:
2119
cm, am, m = match_include_to_path(incname, allow, entry)
2120
if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']):
2121
deleted.append(entry)
2123
for entry in deleted:
2124
profile[allow]['path'].pop(entry)
2127
def delete_duplicates(profile, incname):
2129
# Allow rules covered by denied rules shouldn't be deleted
2130
# only a subset allow rules may actually be denied
2132
if include.get(incname, False):
2133
deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain'])
2135
deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain'])
2137
deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']['capability'])
2139
deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability'])
2141
deleted += delete_path_duplicates(profile, incname, 'allow')
2142
deleted += delete_path_duplicates(profile, incname, 'deny')
2144
elif filelist.get(incname, False):
2145
deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain'])
2147
deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain'])
2149
deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']['capability'])
2151
deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability'])
2153
deleted += delete_path_duplicates(profile, incname, 'allow')
2154
deleted += delete_path_duplicates(profile, incname, 'deny')
2158
def match_net_include(incname, family, type):
2159
includelist = [incname]
2163
name = includelist.pop(0)
2165
checked.append(name)
2166
if netrules_access_check(include[name][name]['allow']['netdomain'], family, type):
2169
if include[name][name]['include'].keys() and name not in checked:
2170
includelist += include[name][name]['include'].keys()
2172
if len(includelist):
2173
name = includelist.pop(0)
2179
def match_cap_includes(profile, cap):
2181
for incname in include.keys():
2182
if valid_include(profile, incname) and include[incname][incname]['allow']['capability'][cap].get('set', False) == 1:
2183
newincludes.append(incname)
2187
def re_match_include(path):
2188
"""Matches the path for include and returns the include path"""
2189
regex_include = re.compile('^\s*#?include\s*<(.*)>\s*(#.*)?$')
2190
match = regex_include.search(path)
2192
return match.groups()[0]
2196
def valid_include(profile, incname):
2197
if profile and profile['include'].get(incname, False):
2200
if cfg['settings']['custom_includes']:
2201
for incm in cfg['settings']['custom_includes'].split():
2205
if incname.startswith('abstractions/') and os.path.isfile(profile_dir + '/' + incname):
2210
def match_net_includes(profile, family, nettype):
2212
for incname in include.keys():
2214
if valid_include(profile, incname) and match_net_include(incname, family, type):
2215
newincludes.append(incname)
2219
def do_logprof_pass(logmark='', passno=0, pid=pid):
2220
# set up variables for this pass
2222
# transitions = hasher()
2223
# seen = hasher() # XXX global?
2226
global existing_profiles
2229
# profile_changes = hasher()
2231
# log_dict = hasher()
2233
# skip = hasher() # XXX global?
2234
# filelist = hasher()
2236
aaui.UI_Info(_('Reading log entries from %s.') % filename)
2239
aaui.UI_Info(_('Updating AppArmor profiles in %s.') % profile_dir)
2243
sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
2245
#print(existing_profiles)
2246
##if not repo_cf and cfg['repostory']['url']:
2247
## repo_cfg = read_config('repository.conf')
2248
## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']:
2249
## UI_ask_to_enable_repo()
2250
log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log)
2251
log = log_reader.read_log(logmark)
2255
handle_children('', '', root)
2256
#for root in range(len(log)):
2257
#log[root] = handle_children('', '', log[root])
2259
for pid in sorted(profile_changes.keys()):
2260
set_process(pid, profile_changes[pid])
2266
if aaui.UI_mode == 'yast':
2271
# Check for finished
2274
##if not repo_cfg['repository'].get('upload', False) or repo['repository']['upload'] == 'later':
2275
## UI_ask_to_upload_profiles()
2276
##if repo_enabled():
2277
## if repo_cgf['repository']['upload'] == 'yes':
2281
# If user selects 'Finish' then we want to exit logprof
2288
def save_profiles():
2289
# Ensure the changed profiles are actual active profiles
2290
for prof_name in changed.keys():
2291
if not is_active_profile(prof_name):
2292
changed.pop(prof_name)
2294
changed_list = sorted(changed.keys())
2298
if aaui.UI_mode == 'yast':
2300
# selected_profiles = [] # XXX selected_profiles_ref?
2301
profile_changes = dict()
2302
for prof in changed_list:
2303
oldprofile = serialize_profile(original_aa[prof], prof)
2304
newprofile = serialize_profile(aa[prof], prof)
2305
profile_changes[prof] = get_profile_diff(oldprofile, newprofile)
2306
explanation = _('Select which profile changes you would like to save to the\nlocal profile set.')
2307
title = _('Local profile changes')
2308
SendDataToYast({'type': 'dialog-select-profiles',
2310
'explanation': explanation,
2311
'dialog_select': 'true',
2312
'get_changelog': 'false',
2313
'profiles': profile_changes
2315
ypath, yarg = GetDataFromYast()
2316
if yarg['STATUS'] == 'cancel':
2319
selected_profiles_ref = yarg['PROFILES']
2320
for profile_name in selected_profiles_ref:
2321
write_profile_ui_feedback(profile_name)
2322
reload_base(profile_name)
2326
q['title'] = 'Changed Local Profiles'
2328
q['explanation'] = _('The following local profiles were changed. Would you like to save them?')
2329
q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT']
2330
q['default'] = 'CMD_VIEW_CHANGES'
2331
q['options'] = changed
2335
while ans != 'CMD_SAVE_CHANGES':
2338
ans, arg = aaui.UI_PromptUser(q)
2339
if ans == 'CMD_SAVE_SELECTED':
2340
profile_name = list(changed.keys())[arg]
2341
write_profile_ui_feedback(profile_name)
2342
reload_base(profile_name)
2343
#changed.pop(profile_name)
2344
#q['options'] = changed
2346
elif ans == 'CMD_VIEW_CHANGES':
2347
which = list(changed.keys())[arg]
2349
if aa[which][which].get('filename', False):
2350
oldprofile = aa[which][which]['filename']
2352
oldprofile = get_profile_filename(which)
2353
newprofile = serialize_profile_from_old_profile(aa[which], which, '')
2355
display_changes_with_comments(oldprofile, newprofile)
2357
elif ans == 'CMD_VIEW_CHANGES_CLEAN':
2358
which = list(changed.keys())[arg]
2359
oldprofile = serialize_profile(original_aa[which], which, '')
2360
newprofile = serialize_profile(aa[which], which, '')
2362
display_changes(oldprofile, newprofile)
2364
for profile_name in changed_list:
2365
write_profile_ui_feedback(profile_name)
2366
reload_base(profile_name)
2371
def generate_diff(oldprofile, newprofile):
2372
oldtemp = tempfile.NamedTemporaryFile('w')
2374
oldtemp.write(oldprofile)
2377
newtemp = tempfile.NamedTemporaryFile('w')
2378
newtemp.write(newprofile)
2381
difftemp = tempfile.NamedTemporaryFile('w', delete=False)
2383
subprocess.call('diff -u -p %s %s > %s' % (oldtemp.name, newtemp.name, difftemp.name), shell=True)
2389
def get_profile_diff(oldprofile, newprofile):
2390
difftemp = generate_diff(oldprofile, newprofile)
2392
with open_file_read(difftemp.name) as f_in:
2394
if not (line.startswith('---') and line .startswith('+++') and line.startswith('@@')):
2397
difftemp.delete = True
2399
return ''.join(diff)
2401
def display_changes(oldprofile, newprofile):
2402
if aaui.UI_mode == 'yast':
2403
aaui.UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile))
2405
difftemp = generate_diff(oldprofile, newprofile)
2406
subprocess.call('less %s' % difftemp.name, shell=True)
2407
difftemp.delete = True
2410
def display_changes_with_comments(oldprofile, newprofile):
2411
"""Compare the new profile with the existing profile inclusive of all the comments"""
2412
if not os.path.exists(oldprofile):
2413
raise AppArmorException(_("Can't find existing profile %s to compare changes.") % oldprofile)
2414
if aaui.UI_mode == 'yast':
2418
newtemp = tempfile.NamedTemporaryFile('w')
2419
newtemp.write(newprofile)
2422
difftemp = tempfile.NamedTemporaryFile('w')
2424
subprocess.call('diff -u -p %s %s > %s' % (oldprofile, newtemp.name, difftemp.name), shell=True)
2427
subprocess.call('less %s' % difftemp.name, shell=True)
2430
def set_process(pid, profile):
2431
# If process not running don't do anything
2432
if not os.path.exists('/proc/%s/attr/current' % pid):
2437
process = open_file_read('/proc/%s/attr/current' % pid)
2440
current = process.readline().strip()
2443
if not re.search('^null(-complain)*-profile$', current):
2448
stats = open_file_read('/proc/%s/stat' % pid)
2451
stat = stats.readline().strip()
2454
match = re.search('^\d+ \((\S+)\) ', stat)
2459
process = open_file_write('/proc/%s/attr/current' % pid)
2462
process.write('setprofile %s' % profile)
2466
for aamode in prelog.keys():
2467
for profile in prelog[aamode].keys():
2468
for hat in prelog[aamode][profile].keys():
2470
for path in prelog[aamode][profile][hat]['path'].keys():
2471
mode = prelog[aamode][profile][hat]['path'][path]
2473
combinedmode = set()
2474
# Is path in original profile?
2475
if aa[profile][hat]['allow']['path'].get(path, False):
2476
combinedmode |= aa[profile][hat]['allow']['path'][path]['mode']
2478
# Match path to regexps in profile
2479
combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0]
2481
# Match path from includes
2483
combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0]
2485
if not combinedmode or not mode_contains(combinedmode, mode):
2486
if log_dict[aamode][profile][hat]['path'].get(path, False):
2487
mode |= log_dict[aamode][profile][hat]['path'][path]
2489
log_dict[aamode][profile][hat]['path'][path] = mode
2491
for capability in prelog[aamode][profile][hat]['capability'].keys():
2492
# If capability not already in profile
2493
if not aa[profile][hat]['allow']['capability'][capability].get('set', False):
2494
log_dict[aamode][profile][hat]['capability'][capability] = True
2496
nd = prelog[aamode][profile][hat]['netdomain']
2497
for family in nd.keys():
2498
for sock_type in nd[family].keys():
2499
if not profile_known_network(aa[profile][hat], family, sock_type):
2500
log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True
2503
def validate_profile_mode(mode, allow, nt_name=None):
2505
pattern = '^(%s)+$' % PROFILE_MODE_DENY_RE.pattern
2506
if re.search(pattern, mode):
2512
pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern
2513
if re.search(pattern, mode):
2519
pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern
2520
if re.search(pattern, mode):
2525
# rpm backup files, dotfiles, emacs backup files should not be processed
2526
# The skippable files type needs be synced with apparmor initscript
2527
def is_skippable_file(path):
2528
"""Returns True if filename matches something to be skipped"""
2529
if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path)
2530
or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path)
2531
or path[-1] == '~' or path == 'README'):
2534
def is_skippable_dir(path):
2535
if re.search('(disable|cache|force-complain|lxc)', path):
2539
def check_include_syntax(errors):
2543
def check_profile_syntax(errors):
2547
def read_profiles():
2549
os.listdir(profile_dir)
2551
fatal_error(_("Can't read AppArmor profiles in %s") % profile_dir)
2553
for file in os.listdir(profile_dir):
2554
if os.path.isfile(profile_dir + '/' + file):
2555
if is_skippable_file(file):
2558
read_profile(profile_dir + '/' + file, True)
2560
def read_inactive_profiles():
2561
if not os.path.exists(extra_profile_dir):
2564
os.listdir(profile_dir)
2566
fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir)
2568
for file in os.listdir(profile_dir):
2569
if os.path.isfile(extra_profile_dir + '/' + file):
2570
if is_skippable_file(file):
2573
read_profile(extra_profile_dir + '/' + file, False)
2575
def read_profile(file, active_profile):
2578
with open_file_read(file) as f_in:
2579
data = f_in.readlines()
2581
debug_logger.debug("read_profile: can't read %s - skipping" % file)
2584
profile_data = parse_profile_data(data, file, 0)
2586
if profile_data and active_profile:
2587
attach_profile_data(aa, profile_data)
2588
attach_profile_data(original_aa, profile_data)
2590
attach_profile_data(extras, profile_data)
2593
def attach_profile_data(profiles, profile_data):
2594
# Make deep copy of data to avoid changes to
2595
# arising due to mutables
2596
for p in profile_data.keys():
2597
profiles[p] = deepcopy(profile_data[p])
2599
## Profile parsing regex
2600
RE_PROFILE_START = re.compile('^\s*(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$')
2601
RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$')
2602
RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$')
2603
RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$')
2604
RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$')
2605
RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$')
2606
RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$')
2607
RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE)
2608
RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$')
2609
RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$')
2610
RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$')
2611
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
2612
RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$')
2613
RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$')
2614
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$')
2615
RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$')
2616
RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$')
2617
RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$')
2618
RE_PROFILE_DBUS = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*\s*,)\s*(#.*)?$')
2620
# match anything that's not " or #, or matching quotes with anything except quotes inside
2621
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*'
2623
RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash +
2624
',\s*(#.*)?$') # match comma plus any trailing comment
2625
RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group
2626
'(?P<comment>#.*)$') # match trailing comment and store in 'comment' group
2628
def parse_profile_data(data, file, do_include):
2629
profile_data = hasher()
2632
in_contained_hat = None
2634
parsed_profiles = []
2635
initial_comment = ''
2641
for lineno, line in enumerate(data):
2645
# we're dealing with a multiline statement
2647
line = '%s %s' % (lastline, line)
2649
# Starting line of a profile
2650
if RE_PROFILE_START.search(line):
2651
matches = RE_PROFILE_START.search(line).groups()
2654
#print(profile, hat)
2655
if profile != hat or not matches[3]:
2656
raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno + 1))
2657
# Keep track of the start of a profile
2658
if profile and profile == hat and matches[3]:
2661
in_contained_hat = True
2662
profile_data[profile][hat]['profile'] = True
2665
profile = matches[1]
2667
profile = matches[3]
2669
if len(profile.split('//')) >= 2:
2670
profile, hat = profile.split('//')[:2]
2673
in_contained_hat = False
2675
profile_data[profile][hat]['external'] = True
2679
existing_profiles[profile] = file
2683
profile = strip_quotes(profile)
2685
hat = strip_quotes(hat)
2686
# save profile name and filename
2687
profile_data[profile][hat]['name'] = profile
2688
profile_data[profile][hat]['filename'] = file
2689
filelist[file]['profiles'][profile][hat] = True
2691
profile_data[profile][hat]['flags'] = flags
2693
profile_data[profile][hat]['allow']['netdomain'] = hasher()
2694
profile_data[profile][hat]['allow']['path'] = hasher()
2695
profile_data[profile][hat]['allow']['dbus'] = list()
2696
# Save the initial comment
2698
profile_data[profile][hat]['initial_comment'] = initial_comment
2700
initial_comment = ''
2703
profile_data[profile][profile]['repo']['url'] = repo_data['url']
2704
profile_data[profile][profile]['repo']['user'] = repo_data['user']
2706
elif RE_PROFILE_END.search(line):
2707
# If profile ends and we're not in one
2709
raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno + 1))
2711
if in_contained_hat:
2713
in_contained_hat = False
2715
parsed_profiles.append(profile)
2718
initial_comment = ''
2720
elif RE_PROFILE_CAP.search(line):
2721
matches = RE_PROFILE_CAP.search(line).groups()
2724
raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno + 1))
2731
if matches[1] and matches[1].strip() == 'deny':
2734
capability = matches[2]
2736
profile_data[profile][hat][allow]['capability'][capability]['set'] = True
2737
profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit
2739
elif RE_PROFILE_LINK.search(line):
2740
matches = RE_PROFILE_LINK.search(line).groups()
2743
raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno + 1))
2750
if matches[1] and matches[1].strip() == 'deny':
2754
link = strip_quotes(matches[6])
2755
value = strip_quotes(matches[7])
2756
profile_data[profile][hat][allow]['link'][link]['to'] = value
2757
profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | apparmor.aamode.AA_MAY_LINK
2760
profile_data[profile][hat][allow]['link'][link]['mode'] |= apparmor.aamode.AA_LINK_SUBSET
2763
profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | apparmor.aamode.AA_LINK_SUBSET
2765
profile_data[profile][hat][allow]['link'][link]['audit'] = set()
2767
elif RE_PROFILE_CHANGE_PROFILE.search(line):
2768
matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups()
2771
raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno + 1))
2773
cp = strip_quotes(matches[0])
2774
profile_data[profile][hat]['changes_profile'][cp] = True
2776
elif RE_PROFILE_ALIAS.search(line):
2777
matches = RE_PROFILE_ALIAS.search(line).groups()
2779
from_name = strip_quotes(matches[0])
2780
to_name = strip_quotes(matches[1])
2783
profile_data[profile][hat]['alias'][from_name] = to_name
2785
if not filelist.get(file, False):
2786
filelist[file] = hasher()
2787
filelist[file]['alias'][from_name] = to_name
2789
elif RE_PROFILE_RLIMIT.search(line):
2790
matches = RE_PROFILE_RLIMIT.search(line).groups()
2793
raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno + 1))
2795
from_name = matches[0]
2796
to_name = matches[2]
2798
profile_data[profile][hat]['rlimit'][from_name] = to_name
2800
elif RE_PROFILE_BOOLEAN.search(line):
2801
matches = RE_PROFILE_BOOLEAN.search(line)
2804
raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno + 1))
2806
bool_var = matches[0]
2809
profile_data[profile][hat]['lvar'][bool_var] = value
2811
elif RE_PROFILE_VARIABLE.search(line):
2812
# variable additions += and =
2813
matches = RE_PROFILE_VARIABLE.search(line).groups()
2815
list_var = strip_quotes(matches[0])
2816
var_operation = matches[1]
2817
value = strip_quotes(matches[2])
2820
if not profile_data[profile][hat].get('lvar', False):
2821
profile_data[profile][hat]['lvar'][list_var] = []
2822
store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation)
2824
if not filelist[file].get('lvar', False):
2825
filelist[file]['lvar'][list_var] = []
2826
store_list_var(filelist[file]['lvar'], list_var, value, var_operation)
2828
elif RE_PROFILE_CONDITIONAL.search(line):
2829
# Conditional Boolean
2832
elif RE_PROFILE_CONDITIONAL_VARIABLE.search(line):
2833
# Conditional Variable defines
2836
elif RE_PROFILE_CONDITIONAL_BOOLEAN.search(line):
2837
# Conditional Boolean defined
2840
elif RE_PROFILE_PATH_ENTRY.search(line):
2841
matches = RE_PROFILE_PATH_ENTRY.search(line).groups()
2844
raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno + 1))
2851
if matches[1] and matches[1].strip() == 'deny':
2858
path = matches[3].strip()
2860
nt_name = matches[6]
2862
nt_name = nt_name.strip()
2864
p_re = convert_regexp(path)
2868
raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno + 1))
2870
if not validate_profile_mode(mode, allow, nt_name):
2871
raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1))
2875
tmpmode = str_to_mode('%s::' % mode)
2877
tmpmode = str_to_mode(mode)
2879
profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode
2882
profile_data[profile][hat][allow]['path'][path]['to'] = nt_name
2885
profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', set()) | tmpmode
2887
profile_data[profile][hat][allow]['path'][path]['audit'] = set()
2889
elif re_match_include(line):
2891
include_name = re_match_include(line)
2892
if include_name.startswith('local/'):
2893
profile_data[profile][hat]['localinclude'][include_name] = True
2896
profile_data[profile][hat]['include'][include_name] = True
2898
if not filelist.get(file):
2899
filelist[file] = hasher()
2900
filelist[file]['include'][include_name] = True
2901
# If include is a directory
2902
if os.path.isdir(profile_dir + '/' + include_name):
2903
for path in os.listdir(profile_dir + '/' + include_name):
2905
if is_skippable_file(path):
2907
if os.path.isfile(profile_dir + '/' + include_name + '/' + path):
2908
file_name = include_name + '/' + path
2909
file_name = file_name.replace(profile_dir + '/', '')
2910
if not include.get(file_name, False):
2911
load_include(file_name)
2913
if not include.get(include_name, False):
2914
load_include(include_name)
2916
elif RE_PROFILE_NETWORK.search(line):
2917
matches = RE_PROFILE_NETWORK.search(line).groups()
2920
raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno + 1))
2926
if matches[1] and matches[1].strip() == 'deny':
2928
network = matches[2]
2930
if RE_NETWORK_FAMILY_TYPE.search(network):
2931
nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups()
2932
fam, typ = nmatch[:2]
2933
##Simply ignore any type subrules if family has True (seperately for allow and deny)
2934
##This will lead to those type specific rules being lost when written
2935
#if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict:
2936
profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1
2937
profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit
2938
elif RE_NETWORK_FAMILY.search(network):
2939
fam = RE_NETWORK_FAMILY.search(network).groups()[0]
2940
profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True
2941
profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit
2943
profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True
2944
profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True
2946
elif RE_PROFILE_DBUS.search(line):
2947
matches = RE_PROFILE_DBUS.search(line).groups()
2950
raise AppArmorException(_('Syntax Error: Unexpected dbus entry found in file: %s line: %s') % (file, lineno + 1))
2956
if matches[1] and matches[1].strip() == 'deny':
2960
#parse_dbus_rule(profile_data[profile], dbus, audit, allow)
2961
dbus_rule = parse_dbus_rule(dbus)
2962
dbus_rule.audit = audit
2963
dbus_rule.deny = (allow == 'deny')
2965
dbus_rules = profile_data[profile][hat][allow].get('dbus', list())
2966
dbus_rules.append(dbus_rule)
2967
profile_data[profile][hat][allow]['dbus'] = dbus_rules
2969
elif RE_PROFILE_CHANGE_HAT.search(line):
2970
matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
2973
raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno + 1))
2976
hat = strip_quotes(hat)
2978
if not profile_data[profile][hat].get('declared', False):
2979
profile_data[profile][hat]['declared'] = True
2981
elif RE_PROFILE_HAT_DEF.search(line):
2982
# An embedded hat syntax definition starts
2983
matches = RE_PROFILE_HAT_DEF.search(line).groups()
2985
raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno + 1))
2987
in_contained_hat = True
2989
hat = strip_quotes(hat)
2992
profile_data[profile][hat]['flags'] = flags
2993
profile_data[profile][hat]['declared'] = False
2994
#profile_data[profile][hat]['allow']['path'] = hasher()
2995
#profile_data[profile][hat]['allow']['netdomain'] = hasher()
2998
profile_data[profile][hat]['initial_comment'] = initial_comment
2999
initial_comment = ''
3000
if filelist[file]['profiles'][profile].get(hat, False):
3001
raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') % (hat, profile))
3002
filelist[file]['profiles'][profile][hat] = True
3004
elif line[0] == '#':
3005
# Handle initial comments
3007
if line.startswith('# Last Modified:'):
3009
elif line.startswith('# REPOSITORY:'): # TODO: allow any number of spaces/tabs
3010
parts = line.split()
3011
if len(parts) == 3 and parts[2] == 'NEVERSUBMIT':
3012
repo_data = {'neversubmit': True}
3013
elif len(parts) == 5:
3014
repo_data = {'url': parts[2],
3018
aaui.UI_Important(_('Warning: invalid "REPOSITORY:" line in %s, ignoring.') % file)
3019
initial_comment = initial_comment + line + '\n'
3021
initial_comment = initial_comment + line + '\n'
3023
elif not RE_RULE_HAS_COMMA.search(line):
3024
# Bah, line continues on to the next line
3025
if RE_HAS_COMMENT_SPLIT.search(line):
3026
# filter trailing comments
3027
lastline = RE_HAS_COMMENT_SPLIT.search(line).group('not_comment')
3031
raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno + 1))
3033
# Below is not required I'd say
3035
for hatglob in cfg['required_hats'].keys():
3036
for parsed_prof in sorted(parsed_profiles):
3037
if re.search(hatglob, parsed_prof):
3038
for hat in cfg['required_hats'][hatglob].split():
3039
if not profile_data[parsed_prof].get(hat, False):
3040
profile_data[parsed_prof][hat] = hasher()
3042
# End of file reached but we're stuck in a profile
3043
if profile and not do_include:
3044
raise AppArmorException(_("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s") % (file, profile))
3048
# RE_DBUS_ENTRY = re.compile('^dbus\s*()?,\s*$')
3049
# use stuff like '(?P<action>(send|write|w|receive|read|r|rw))'
3051
def parse_dbus_rule(line):
3052
# XXX Do real parsing here
3053
return aarules.Raw_DBUS_Rule(line)
3055
#matches = RE_DBUS_ENTRY.search(line).groups()
3056
#if len(matches) == 1:
3059
# print('no matches')
3060
# return aarules.DBUS_Rule()
3063
def separate_vars(vs):
3064
"""Returns a list of all the values for a variable"""
3067
#data = [i.strip('"') for i in vs.split()]
3068
RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$')
3069
while RE_VARS.search(vs):
3070
matches = RE_VARS.search(vs).groups()
3071
data.append(strip_quotes(matches[0]))
3076
def is_active_profile(pname):
3077
if aa.get(pname, False):
3082
def store_list_var(var, list_var, value, var_operation):
3083
"""Store(add new variable or add values to variable) the variables encountered in the given list_var"""
3084
vlist = separate_vars(value)
3085
if var_operation == '=':
3086
if not var.get(list_var, False):
3087
var[list_var] = set(vlist)
3089
#print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var])
3090
raise AppArmorException(_('An existing variable redefined: %s') % list_var)
3091
elif var_operation == '+=':
3092
if var.get(list_var, False):
3093
var[list_var] = set(var[list_var] + vlist)
3095
raise AppArmorException(_('Values added to a non-existing variable: %s') % list_var)
3097
raise AppArmorException(_('Unknown variable operation: %s') % var_operation)
3100
def strip_quotes(data):
3101
if data[0] + data[-1] == '""':
3106
def quote_if_needed(data):
3107
# quote data if it contains whitespace
3109
data = '"' + data + '"'
3113
escape = strip_quotes(escape)
3114
escape = re.sub('((?<!\\))"', r'\1\\', escape)
3115
if re.search('(\s|^$|")', escape):
3116
return '"%s"' % escape
3119
def write_header(prof_data, depth, name, embedded_hat, write_flags):
3122
name = quote_if_needed(name)
3124
if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]', name)):
3125
name = 'profile %s' % name
3127
if write_flags and prof_data['flags']:
3128
data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags']))
3130
data.append('%s%s {' % (pre, name))
3134
def write_single(prof_data, depth, allow, name, prefix, tail):
3137
ref, allow = set_ref_allow(prof_data, allow)
3139
if ref.get(name, False):
3140
for key in sorted(ref[name].keys()):
3141
qkey = quote_if_needed(key)
3142
data.append('%s%s%s%s%s' % (pre, allow, prefix, qkey, tail))
3143
if ref[name].keys():
3148
def set_allow_str(allow):
3151
elif allow == 'allow':
3156
raise AppArmorException(_("Invalid allow string: %(allow)s"))
3158
def set_ref_allow(prof_data, allow):
3160
return prof_data[allow], set_allow_str(allow)
3162
return prof_data, ''
3165
def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn):
3168
ref, allow = set_ref_allow(prof_data, allow)
3170
if ref.get(name, False):
3171
for key in sorted(ref[name].keys()):
3172
value = fn(ref[name][key]) # eval('%s(%s)' % (fn, ref[name][key]))
3173
data.append('%s%s%s%s%s%s' % (pre, allow, prefix, key, sep, value))
3174
if ref[name].keys():
3179
def write_includes(prof_data, depth):
3180
return write_single(prof_data, depth, '', 'include', '#include <', '>')
3182
def write_change_profile(prof_data, depth):
3183
return write_single(prof_data, depth, '', 'change_profile', 'change_profile -> ', ',')
3185
def write_alias(prof_data, depth):
3186
return write_pair(prof_data, depth, '', 'alias', 'alias ', ' -> ', ',', quote_if_needed)
3188
def write_rlimits(prof_data, depth):
3189
return write_pair(prof_data, depth, '', 'rlimit', 'set rlimit ', ' <= ', ',', quote_if_needed)
3191
def var_transform(ref):
3194
data.append(quote_if_needed(value))
3195
return ' '.join(data)
3197
def write_list_vars(prof_data, depth):
3198
return write_pair(prof_data, depth, '', 'lvar', '', ' = ', '', var_transform)
3200
def write_cap_rules(prof_data, depth, allow):
3203
allowstr = set_allow_str(allow)
3205
if prof_data[allow].get('capability', False):
3206
for cap in sorted(prof_data[allow]['capability'].keys()):
3208
if prof_data[allow]['capability'][cap].get('audit', False):
3210
if prof_data[allow]['capability'][cap].get('set', False):
3211
data.append('%s%s%scapability %s,' % (pre, audit, allowstr, cap))
3216
def write_capabilities(prof_data, depth):
3217
#data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',')
3218
data = write_cap_rules(prof_data, depth, 'deny')
3219
data += write_cap_rules(prof_data, depth, 'allow')
3222
def write_net_rules(prof_data, depth, allow):
3225
allowstr = set_allow_str(allow)
3227
if prof_data[allow].get('netdomain', False):
3228
if prof_data[allow]['netdomain'].get('rule', False) == 'all':
3229
if prof_data[allow]['netdomain']['audit'].get('all', False):
3231
data.append('%s%snetwork,' % (pre, audit))
3233
for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()):
3234
if prof_data[allow]['netdomain']['rule'][fam] is True:
3235
if prof_data[allow]['netdomain']['audit'][fam]:
3237
data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam))
3239
for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()):
3240
if prof_data[allow]['netdomain']['audit'][fam].get(typ, False):
3242
data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ))
3243
if prof_data[allow].get('netdomain', False):
3248
def write_netdomain(prof_data, depth):
3249
data = write_net_rules(prof_data, depth, 'deny')
3250
data += write_net_rules(prof_data, depth, 'allow')
3253
def write_dbus_rules(prof_data, depth, allow):
3257
# no dbus rules, so return
3258
if not prof_data[allow].get('dbus', False):
3261
for dbus_rule in prof_data[allow]['dbus']:
3262
data.append('%s%s' % (pre, dbus_rule.serialize()))
3266
def write_dbus(prof_data, depth):
3267
data = write_dbus_rules(prof_data, depth, 'deny')
3268
data += write_net_rules(prof_data, depth, 'allow')
3271
def write_link_rules(prof_data, depth, allow):
3274
allowstr = set_allow_str(allow)
3276
if prof_data[allow].get('link', False):
3277
for path in sorted(prof_data[allow]['link'].keys()):
3278
to_name = prof_data[allow]['link'][path]['to']
3280
if prof_data[allow]['link'][path]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
3283
if prof_data[allow]['link'][path].get('audit', False):
3285
path = quote_if_needed(path)
3286
to_name = quote_if_needed(to_name)
3287
data.append('%s%s%slink %s%s -> %s,' % (pre, audit, allowstr, subset, path, to_name))
3292
def write_links(prof_data, depth):
3293
data = write_link_rules(prof_data, depth, 'deny')
3294
data += write_link_rules(prof_data, depth, 'allow')
3298
def write_path_rules(prof_data, depth, allow):
3301
allowstr = set_allow_str(allow)
3303
if prof_data[allow].get('path', False):
3304
for path in sorted(prof_data[allow]['path'].keys()):
3305
mode = prof_data[allow]['path'][path]['mode']
3306
audit = prof_data[allow]['path'][path]['audit']
3308
if prof_data[allow]['path'][path].get('to', False):
3309
tail = ' -> %s' % prof_data[allow]['path'][path]['to']
3310
user, other = split_mode(mode)
3311
user_audit, other_audit = split_mode(audit)
3313
while user or other:
3318
# if no other mode set
3320
tmpmode = user - other
3321
tmpaudit = user_audit
3322
user = user - tmpmode
3324
if user_audit - other_audit & user:
3326
tmpaudit = user_audit - other_audit & user
3327
tmpmode = user & tmpaudit
3328
user = user - tmpmode
3331
tmpmode = user | other
3332
tmpaudit = user_audit | other_audit
3333
user = user - tmpmode
3334
other = other - tmpmode
3336
if tmpmode & tmpaudit:
3337
modestr = mode_to_str(tmpmode & tmpaudit)
3338
path = quote_if_needed(path)
3339
data.append('%saudit %s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
3340
tmpmode = tmpmode - tmpaudit
3343
modestr = mode_to_str(tmpmode)
3344
path = quote_if_needed(path)
3345
data.append('%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
3350
def write_paths(prof_data, depth):
3351
data = write_path_rules(prof_data, depth, 'deny')
3352
data += write_path_rules(prof_data, depth, 'allow')
3356
def write_rules(prof_data, depth):
3357
data = write_alias(prof_data, depth)
3358
data += write_list_vars(prof_data, depth)
3359
data += write_includes(prof_data, depth)
3360
data += write_rlimits(prof_data, depth)
3361
data += write_capabilities(prof_data, depth)
3362
data += write_netdomain(prof_data, depth)
3363
data += write_dbus(prof_data, depth)
3364
data += write_links(prof_data, depth)
3365
data += write_paths(prof_data, depth)
3366
data += write_change_profile(prof_data, depth)
3370
def write_piece(profile_data, depth, name, nhat, write_flags):
3378
wname = name + '//' + nhat
3381
data += write_header(profile_data[name], depth, wname, False, write_flags)
3382
data += write_rules(profile_data[name], depth + 1)
3384
pre2 = ' ' * (depth + 1)
3385
# External hat declarations
3386
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
3387
if profile_data[hat].get('declared', False):
3388
data.append('%s^%s,' % (pre2, hat))
3392
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
3393
if not profile_data[hat]['external'] and not profile_data[hat]['declared']:
3395
if profile_data[hat]['profile']:
3396
data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, write_flags)))
3398
data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, write_flags)))
3400
data += list(map(str, write_rules(profile_data[hat], depth + 2)))
3402
data.append('%s}' % pre2)
3404
data.append('%s}' % pre)
3407
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
3408
if name == nhat and profile_data[hat].get('external', False):
3410
data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, nhat, write_flags)))
3415
def serialize_profile(profile_data, name, options):
3417
include_metadata = False
3418
include_flags = True
3421
if options: # and type(options) == dict:
3422
if options.get('METADATA', False):
3423
include_metadata = True
3424
if options.get('NO_FLAGS', False):
3425
include_flags = False
3427
if include_metadata:
3428
string = '# Last Modified: %s\n' % time.asctime()
3430
if (profile_data[name].get('repo', False) and
3431
profile_data[name]['repo']['url'] and
3432
profile_data[name]['repo']['user'] and
3433
profile_data[name]['repo']['id']):
3434
repo = profile_data[name]['repo']
3435
string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
3436
elif profile_data[name]['repo']['neversubmit']:
3437
string += '# REPOSITORY: NEVERSUBMIT\n'
3439
# if profile_data[name].get('initial_comment', False):
3440
# comment = profile_data[name]['initial_comment']
3441
# comment.replace('\\n', '\n')
3442
# string += comment + '\n'
3444
prof_filename = get_profile_filename(name)
3445
if filelist.get(prof_filename, False):
3446
data += write_alias(filelist[prof_filename], 0)
3447
data += write_list_vars(filelist[prof_filename], 0)
3448
data += write_includes(filelist[prof_filename], 0)
3450
#Here should be all the profiles from the files added write after global/common stuff
3451
for prof in sorted(filelist[prof_filename]['profiles'].keys()):
3453
if original_aa[prof][prof].get('initial_comment', False):
3454
comment = profile_data[name]['initial_comment']
3455
comment.replace('\\n', '\n')
3456
data += [comment + '\n']
3457
data += write_piece(original_aa[prof], 0, prof, prof, include_flags)
3459
if profile_data[name].get('initial_comment', False):
3460
comment = profile_data[name]['initial_comment']
3461
comment.replace('\\n', '\n')
3462
data += [comment + '\n']
3463
data += write_piece(profile_data, 0, name, name, include_flags)
3465
string += '\n'.join(data)
3467
return string + '\n'
3469
def serialize_profile_from_old_profile(profile_data, name, options):
3472
include_metadata = False
3473
include_flags = True
3474
prof_filename = get_profile_filename(name)
3476
write_filelist = deepcopy(filelist[prof_filename])
3477
write_prof_data = deepcopy(profile_data)
3479
if options: # and type(options) == dict:
3480
if options.get('METADATA', False):
3481
include_metadata = True
3482
if options.get('NO_FLAGS', False):
3483
include_flags = False
3485
if include_metadata:
3486
string = '# Last Modified: %s\n' % time.asctime()
3488
if (profile_data[name].get('repo', False) and
3489
profile_data[name]['repo']['url'] and
3490
profile_data[name]['repo']['user'] and
3491
profile_data[name]['repo']['id']):
3492
repo = profile_data[name]['repo']
3493
string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
3494
elif profile_data[name]['repo']['neversubmit']:
3495
string += '# REPOSITORY: NEVERSUBMIT\n'
3497
if not os.path.isfile(prof_filename):
3498
raise AppArmorException(_("Can't find existing profile to modify"))
3500
# profiles_list = filelist[prof_filename].keys() # XXX
3502
with open_file_read(prof_filename) as f_in:
3505
write_methods = {'alias': write_alias,
3506
'lvar': write_list_vars,
3507
'include': write_includes,
3508
'rlimit': write_rlimits,
3509
'capability': write_capabilities,
3510
'netdomain': write_netdomain,
3512
'link': write_links,
3513
'path': write_paths,
3514
'change_profile': write_change_profile,
3516
# prof_correct = True # XXX correct?
3517
segments = {'alias': False,
3521
'capability': False,
3526
'change_profile': False,
3527
'include_local_started': False,
3529
#data.append('reading prof')
3532
line = line.rstrip('\n')
3533
#data.append(' ')#data.append('read: '+line)
3534
if RE_PROFILE_START.search(line):
3535
matches = RE_PROFILE_START.search(line).groups()
3536
if profile and profile == hat and matches[3]:
3538
in_contained_hat = True
3539
if write_prof_data[profile][hat]['profile']:
3543
profile = matches[1]
3545
profile = matches[3]
3546
if len(profile.split('//')) >= 2:
3547
profile, hat = profile.split('//')[:2]
3550
in_contained_hat = False
3551
if hat and not write_prof_data[profile][hat]['external']:
3557
profile = strip_quotes(profile)
3559
hat = strip_quotes(hat)
3561
if not write_prof_data[hat]['name'] == profile:
3564
if not write_filelist['profiles'][profile][hat] is True:
3567
if not write_prof_data[hat]['flags'] == flags:
3570
#Write the profile start
3573
data += write_alias(write_filelist, 0)
3574
data += write_list_vars(write_filelist, 0)
3575
data += write_includes(write_filelist, 0)
3578
if write_prof_data[hat]['name'] == profile:
3579
depth = len(line) - len(line.lstrip())
3580
data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags)
3582
elif RE_PROFILE_END.search(line):
3583
# DUMP REMAINDER OF PROFILE
3585
depth = len(line) - len(line.lstrip())
3586
if True in segments.values():
3587
for segs in list(filter(lambda x: segments[x], segments.keys())):
3589
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3590
segments[segs] = False
3591
if write_prof_data[name]['allow'].get(segs, False):
3592
write_prof_data[name]['allow'].pop(segs)
3593
if write_prof_data[name]['deny'].get(segs, False):
3594
write_prof_data[name]['deny'].pop(segs)
3596
data += write_alias(write_prof_data[name], depth)
3597
data += write_list_vars(write_prof_data[name], depth)
3598
data += write_includes(write_prof_data[name], depth)
3599
data += write_rlimits(write_prof_data, depth)
3600
data += write_capabilities(write_prof_data[name], depth)
3601
data += write_netdomain(write_prof_data[name], depth)
3602
data += write_dbus(write_prof_data[name], depth)
3603
data += write_links(write_prof_data[name], depth)
3604
data += write_paths(write_prof_data[name], depth)
3605
data += write_change_profile(write_prof_data[name], depth)
3607
write_prof_data.pop(name)
3609
#Append local includes
3612
if not in_contained_hat:
3614
depth = int((len(line) - len(line.lstrip())) / 2)
3615
pre2 = ' ' * (depth + 1)
3616
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
3617
if not profile_data[hat]['external'] and not profile_data[hat]['declared']:
3619
if profile_data[hat]['profile']:
3620
data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags)))
3622
data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags)))
3624
data += list(map(str, write_rules(profile_data[hat], depth + 2)))
3626
data.append('%s}' % pre2)
3629
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
3630
if profile_data[hat].get('external', False):
3632
data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags)))
3635
if in_contained_hat:
3636
#Hat processed, remove it
3638
in_contained_hat = False
3642
elif RE_PROFILE_CAP.search(line):
3643
matches = RE_PROFILE_CAP.search(line).groups()
3649
if matches[1] and matches[1].strip() == 'deny':
3652
capability = matches[2]
3654
if not write_prof_data[hat][allow]['capability'][capability].get('set', False):
3656
if not write_prof_data[hat][allow]['capability'][capability].get(audit, False) == audit:
3660
if not segments['capability'] and True in segments.values():
3661
for segs in list(filter(lambda x: segments[x], segments.keys())):
3662
depth = len(line) - len(line.lstrip())
3663
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3664
segments[segs] = False
3665
if write_prof_data[name]['allow'].get(segs, False):
3666
write_prof_data[name]['allow'].pop(segs)
3667
if write_prof_data[name]['deny'].get(segs, False):
3668
write_prof_data[name]['deny'].pop(segs)
3669
segments['capability'] = True
3670
write_prof_data[hat][allow]['capability'].pop(capability)
3673
#write_prof_data[hat][allow]['capability'][capability].pop(audit)
3679
elif RE_PROFILE_LINK.search(line):
3680
matches = RE_PROFILE_LINK.search(line).groups()
3685
if matches[1] and matches[1].strip() == 'deny':
3689
link = strip_quotes(matches[6])
3690
value = strip_quotes(matches[7])
3691
if not write_prof_data[hat][allow]['link'][link]['to'] == value:
3693
if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK:
3695
if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
3697
if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET:
3701
if not segments['link'] and True in segments.values():
3702
for segs in list(filter(lambda x: segments[x], segments.keys())):
3703
depth = len(line) - len(line.lstrip())
3704
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3705
segments[segs] = False
3706
if write_prof_data[name]['allow'].get(segs, False):
3707
write_prof_data[name]['allow'].pop(segs)
3708
if write_prof_data[name]['deny'].get(segs, False):
3709
write_prof_data[name]['deny'].pop(segs)
3710
segments['link'] = True
3711
write_prof_data[hat][allow]['link'].pop(link)
3717
elif RE_PROFILE_CHANGE_PROFILE.search(line):
3718
matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups()
3719
cp = strip_quotes(matches[0])
3721
if not write_prof_data[hat]['changes_profile'][cp] is True:
3725
if not segments['change_profile'] and True in segments.values():
3726
for segs in list(filter(lambda x: segments[x], segments.keys())):
3727
depth = len(line) - len(line.lstrip())
3728
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3729
segments[segs] = False
3730
if write_prof_data[name]['allow'].get(segs, False):
3731
write_prof_data[name]['allow'].pop(segs)
3732
if write_prof_data[name]['deny'].get(segs, False):
3733
write_prof_data[name]['deny'].pop(segs)
3734
segments['change_profile'] = True
3735
write_prof_data[hat]['change_profile'].pop(cp)
3741
elif RE_PROFILE_ALIAS.search(line):
3742
matches = RE_PROFILE_ALIAS.search(line).groups()
3744
from_name = strip_quotes(matches[0])
3745
to_name = strip_quotes(matches[1])
3748
if not write_prof_data[hat]['alias'][from_name] == to_name:
3751
if not write_filelist['alias'][from_name] == to_name:
3755
if not segments['alias'] and True in segments.values():
3756
for segs in list(filter(lambda x: segments[x], segments.keys())):
3757
depth = len(line) - len(line.lstrip())
3758
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3759
segments[segs] = False
3760
if write_prof_data[name]['allow'].get(segs, False):
3761
write_prof_data[name]['allow'].pop(segs)
3762
if write_prof_data[name]['deny'].get(segs, False):
3763
write_prof_data[name]['deny'].pop(segs)
3764
segments['alias'] = True
3766
write_prof_data[hat]['alias'].pop(from_name)
3768
write_filelist['alias'].pop(from_name)
3774
elif RE_PROFILE_RLIMIT.search(line):
3775
matches = RE_PROFILE_RLIMIT.search(line).groups()
3777
from_name = matches[0]
3778
to_name = matches[2]
3780
if not write_prof_data[hat]['rlimit'][from_name] == to_name:
3784
if not segments['rlimit'] and True in segments.values():
3785
for segs in list(filter(lambda x: segments[x], segments.keys())):
3786
depth = len(line) - len(line.lstrip())
3787
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3788
segments[segs] = False
3789
if write_prof_data[name]['allow'].get(segs, False):
3790
write_prof_data[name]['allow'].pop(segs)
3791
if write_prof_data[name]['deny'].get(segs, False):
3792
write_prof_data[name]['deny'].pop(segs)
3793
segments['rlimit'] = True
3794
write_prof_data[hat]['rlimit'].pop(from_name)
3800
elif RE_PROFILE_BOOLEAN.search(line):
3801
matches = RE_PROFILE_BOOLEAN.search(line).groups()
3802
bool_var = matches[0]
3805
if not write_prof_data[hat]['lvar'][bool_var] == value:
3809
if not segments['lvar'] and True in segments.values():
3810
for segs in list(filter(lambda x: segments[x], segments.keys())):
3811
depth = len(line) - len(line.lstrip())
3812
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3813
segments[segs] = False
3814
if write_prof_data[name]['allow'].get(segs, False):
3815
write_prof_data[name]['allow'].pop(segs)
3816
if write_prof_data[name]['deny'].get(segs, False):
3817
write_prof_data[name]['deny'].pop(segs)
3818
segments['lvar'] = True
3819
write_prof_data[hat]['lvar'].pop(bool_var)
3824
elif RE_PROFILE_VARIABLE.search(line):
3825
matches = RE_PROFILE_VARIABLE.search(line).groups()
3826
list_var = strip_quotes(matches[0])
3827
var_operation = matches[1]
3828
value = strip_quotes(matches[2])
3831
store_list_var(var_set, list_var, value, var_operation)
3832
if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False):
3835
store_list_var(var_set, list_var, value, var_operation)
3836
if not var_set[list_var] == write_filelist['lvar'].get(list_var, False):
3840
if not segments['lvar'] and True in segments.values():
3841
for segs in list(filter(lambda x: segments[x], segments.keys())):
3842
depth = len(line) - len(line.lstrip())
3843
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3844
segments[segs] = False
3845
if write_prof_data[name]['allow'].get(segs, False):
3846
write_prof_data[name]['allow'].pop(segs)
3847
if write_prof_data[name]['deny'].get(segs, False):
3848
write_prof_data[name]['deny'].pop(segs)
3849
segments['lvar'] = True
3851
write_prof_data[hat]['lvar'].pop(list_var)
3853
write_filelist['lvar'].pop(list_var)
3859
elif RE_PROFILE_PATH_ENTRY.search(line):
3860
matches = RE_PROFILE_PATH_ENTRY.search(line).groups()
3865
if matches[1] and matches[1].split() == 'deny':
3872
path = matches[3].strip()
3874
nt_name = matches[6]
3876
nt_name = nt_name.strip()
3880
tmpmode = str_to_mode('%s::' % mode)
3882
tmpmode = str_to_mode(mode)
3884
if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode:
3887
if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name:
3890
if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode:
3894
if not segments['path'] and True in segments.values():
3895
for segs in list(filter(lambda x: segments[x], segments.keys())):
3896
depth = len(line) - len(line.lstrip())
3897
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3898
segments[segs] = False
3899
if write_prof_data[name]['allow'].get(segs, False):
3900
write_prof_data[name]['allow'].pop(segs)
3901
if write_prof_data[name]['deny'].get(segs, False):
3902
write_prof_data[name]['deny'].pop(segs)
3903
segments['path'] = True
3904
write_prof_data[hat][allow]['path'].pop(path)
3910
elif re_match_include(line):
3911
include_name = re_match_include(line)
3913
if write_prof_data[hat]['include'].get(include_name, False):
3914
if not segments['include'] and True in segments.values():
3915
for segs in list(filter(lambda x: segments[x], segments.keys())):
3916
depth = len(line) - len(line.lstrip())
3917
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3918
segments[segs] = False
3919
if write_prof_data[name]['allow'].get(segs, False):
3920
write_prof_data[name]['allow'].pop(segs)
3921
if write_prof_data[name]['deny'].get(segs, False):
3922
write_prof_data[name]['deny'].pop(segs)
3923
segments['include'] = True
3924
write_prof_data[hat]['include'].pop(include_name)
3927
if write_filelist['include'].get(include_name, False):
3928
write_filelist['include'].pop(include_name)
3931
elif RE_PROFILE_NETWORK.search(line):
3932
matches = RE_PROFILE_NETWORK.search(line).groups()
3937
if matches[1] and matches[1].strip() == 'deny':
3939
network = matches[2]
3940
if RE_NETWORK_FAMILY_TYPE.search(network):
3941
nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups()
3942
fam, typ = nmatch[:2]
3943
if write_prof_data[hat][allow]['netdomain']['rule'][fam][typ] and write_prof_data[hat][allow]['netdomain']['audit'][fam][typ] == audit:
3944
write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ)
3945
write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ)
3950
elif RE_NETWORK_FAMILY.search(network):
3951
fam = RE_NETWORK_FAMILY.search(network).groups()[0]
3952
if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit:
3953
write_prof_data[hat][allow]['netdomain']['rule'].pop(fam)
3954
write_prof_data[hat][allow]['netdomain']['audit'].pop(fam)
3959
if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit:
3960
write_prof_data[hat][allow]['netdomain']['rule'].pop('all')
3961
write_prof_data[hat][allow]['netdomain']['audit'].pop('all')
3967
if not segments['netdomain'] and True in segments.values():
3968
for segs in list(filter(lambda x: segments[x], segments.keys())):
3969
depth = len(line) - len(line.lstrip())
3970
data += write_methods[segs](write_prof_data[name], int(depth / 2))
3971
segments[segs] = False
3972
if write_prof_data[name]['allow'].get(segs, False):
3973
write_prof_data[name]['allow'].pop(segs)
3974
if write_prof_data[name]['deny'].get(segs, False):
3975
write_prof_data[name]['deny'].pop(segs)
3976
segments['netdomain'] = True
3978
elif RE_PROFILE_CHANGE_HAT.search(line):
3979
matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
3981
hat = strip_quotes(hat)
3982
if not write_prof_data[hat]['declared']:
3989
elif RE_PROFILE_HAT_DEF.search(line):
3990
matches = RE_PROFILE_HAT_DEF.search(line).groups()
3991
in_contained_hat = True
3993
hat = strip_quotes(hat)
3995
if not write_prof_data[hat]['flags'] == flags:
3997
if not write_prof_data[hat]['declared'] is False:
3999
if not write_filelist['profile'][profile][hat]:
4012
# data.append('prof done')
4013
# if write_filelist:
4014
# data += write_alias(write_filelist, 0)
4015
# data += write_list_vars(write_filelist, 0)
4016
# data += write_includes(write_filelist, 0)
4017
# data.append('from filelist over')
4018
# data += write_piece(write_prof_data, 0, name, name, include_flags)
4020
string += '\n'.join(data)
4022
return string + '\n'
4024
def write_profile_ui_feedback(profile):
4025
aaui.UI_Info(_('Writing updated profile for %s.') % profile)
4026
write_profile(profile)
4028
def write_profile(profile):
4029
prof_filename = None
4030
if aa[profile][profile].get('filename', False):
4031
prof_filename = aa[profile][profile]['filename']
4033
prof_filename = get_profile_filename(profile)
4035
newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir)
4036
if os.path.exists(prof_filename):
4037
shutil.copymode(prof_filename, newprof.name)
4039
#permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write
4040
#os.chmod(newprof.name, permission_600)
4043
serialize_options = {}
4044
serialize_options['METADATA'] = True
4046
profile_string = serialize_profile(aa[profile], profile, serialize_options)
4047
newprof.write(profile_string)
4050
os.rename(newprof.name, prof_filename)
4052
changed.pop(profile)
4053
original_aa[profile] = deepcopy(aa[profile])
4055
def matchliteral(aa_regexp, literal):
4056
p_regexp = '^' + convert_regexp(aa_regexp) + '$'
4059
match = re.search(p_regexp, literal)
4064
def profile_known_exec(profile, typ, exec_target):
4070
cm, am, m = rematchfrag(profile, 'deny', exec_target)
4071
if cm & apparmor.aamode.AA_MAY_EXEC:
4074
cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target)
4075
if cm & apparmor.aamode.AA_MAY_EXEC:
4078
cm, am, m = rematchfrag(profile, 'allow', exec_target)
4079
if cm & apparmor.aamode.AA_MAY_EXEC:
4082
cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target)
4083
if cm & apparmor.aamode.AA_MAY_EXEC:
4088
def profile_known_capability(profile, capname):
4089
if profile['deny']['capability'][capname].get('set', False):
4092
if profile['allow']['capability'][capname].get('set', False):
4095
for incname in profile['include'].keys():
4096
if include[incname][incname]['deny']['capability'][capname].get('set', False):
4098
if include[incname][incname]['allow']['capability'][capname].get('set', False):
4103
def profile_known_network(profile, family, sock_type):
4104
if netrules_access_check(profile['deny']['netdomain'], family, sock_type):
4106
if netrules_access_check(profile['allow']['netdomain'], family, sock_type):
4109
for incname in profile['include'].keys():
4110
if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type):
4112
if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type):
4117
def netrules_access_check(netrules, family, sock_type):
4121
all_net_family = False
4122
net_family_sock = False
4123
if netrules['rule'].get('all', False):
4125
if netrules['rule'].get(family, False) is True:
4126
all_net_family = True
4127
if (netrules['rule'].get(family, False) and
4128
type(netrules['rule'][family]) == dict and
4129
netrules['rule'][family][sock_type]):
4130
net_family_sock = True
4132
if all_net or all_net_family or net_family_sock:
4137
def reload_base(bin_path):
4138
if not check_for_apparmor():
4141
prof_filename = get_profile_filename(bin_path)
4143
subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True)
4145
def reload(bin_path):
4146
bin_path = find_executable(bin_path)
4150
return reload_base(bin_path)
4152
def get_include_data(filename):
4154
filename = profile_dir + '/' + filename
4155
if os.path.exists(filename):
4156
with open_file_read(filename) as f_in:
4157
data = f_in.readlines()
4159
raise AppArmorException(_('File Not Found: %s') % filename)
4162
def load_include(incname):
4163
load_includeslist = [incname]
4164
if include.get(incname, {}).get(incname, False):
4166
while load_includeslist:
4167
incfile = load_includeslist.pop(0)
4168
if os.path.isfile(profile_dir + '/' + incfile):
4169
data = get_include_data(incfile)
4170
incdata = parse_profile_data(data, incfile, True)
4173
# If include is empty, simply push in a placeholder for it
4174
# because other profiles may mention them
4176
incdata[incname] = hasher()
4177
attach_profile_data(include, incdata)
4178
#If the include is a directory means include all subfiles
4179
elif os.path.isdir(profile_dir + '/' + incfile):
4180
load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile)))
4184
def rematchfrag(frag, allow, path):
4185
combinedmode = set()
4186
combinedaudit = set()
4189
return combinedmode, combinedaudit, matches
4190
for entry in frag[allow]['path'].keys():
4191
match = matchliteral(entry, path)
4193
#print(frag[allow]['path'][entry]['mode'])
4194
combinedmode |= frag[allow]['path'][entry].get('mode', set())
4195
combinedaudit |= frag[allow]['path'][entry].get('audit', set())
4196
matches.append(entry)
4198
return combinedmode, combinedaudit, matches
4200
def match_include_to_path(incname, allow, path):
4201
combinedmode = set()
4202
combinedaudit = set()
4204
includelist = [incname]
4206
incfile = str(includelist.pop(0))
4207
# ret = load_include(incfile)
4208
load_include(incfile)
4209
if not include.get(incfile, {}):
4211
cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path)
4212
#print(incfile, cm, am, m)
4218
if include[incfile][incfile][allow]['path'][path]:
4219
combinedmode |= include[incfile][incfile][allow]['path'][path]['mode']
4220
combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit']
4222
if include[incfile][incfile]['include'].keys():
4223
includelist += include[incfile][incfile]['include'].keys()
4225
return combinedmode, combinedaudit, matches
4227
def match_prof_incs_to_path(frag, allow, path):
4228
combinedmode = set()
4229
combinedaudit = set()
4232
includelist = list(frag['include'].keys())
4234
incname = includelist.pop(0)
4235
cm, am, m = match_include_to_path(incname, allow, path)
4241
return combinedmode, combinedaudit, matches
4243
def suggest_incs_for_path(incname, path, allow):
4244
combinedmode = set()
4245
combinedaudit = set()
4248
includelist = [incname]
4250
inc = includelist.pop(0)
4251
cm, am, m = rematchfrag(include[inc][inc], 'allow', path)
4257
if include[inc][inc]['allow']['path'].get(path, False):
4258
combinedmode |= include[inc][inc]['allow']['path'][path]['mode']
4259
combinedaudit |= include[inc][inc]['allow']['path'][path]['audit']
4261
if include[inc][inc]['include'].keys():
4262
includelist += include[inc][inc]['include'].keys()
4264
return combinedmode, combinedaudit, matches
4266
def check_qualifiers(program):
4267
if cfg['qualifiers'].get(program, False):
4268
if cfg['qualifiers'][program] != 'p':
4269
fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") % program)
4272
def get_subdirectories(current_dir):
4273
"""Returns a list of all directories directly inside given directory"""
4274
if sys.version_info < (3, 0):
4275
return os.walk(current_dir).next()[1]
4277
return os.walk(current_dir).__next__()[1]
4280
incdirs = get_subdirectories(profile_dir)
4281
for idir in incdirs:
4282
if is_skippable_dir(idir):
4284
for dirpath, dirname, files in os.walk(profile_dir + '/' + idir):
4285
if is_skippable_dir(dirpath):
4288
if is_skippable_file(fi):
4291
fi = dirpath + '/' + fi
4292
fi = fi.replace(profile_dir + '/', '', 1)
4295
def glob_common(path):
4298
if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path):
4300
libpath = re.sub('[\d\.]+\.so$', '*.so', libpath)
4301
libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath)
4303
globs.append(libpath)
4305
for glob in cfg['globs']:
4306
if re.search(glob, path):
4308
globbedpath = re.sub(glob, cfg['globs'][glob], path)
4309
if globbedpath != path:
4310
globs.append(globbedpath)
4312
return sorted(set(globs))
4314
def combine_name(name1, name2):
4318
return '%s^%s' % (name1, name2)
4320
def split_name(name):
4321
names = name.split('^')
4325
return names[0], names[1]
4326
def commonprefix(new, old):
4327
match = re.search(r'^([^\0]*)[^\0]*(\0\1[^\0]*)*$', '\0'.join([new, old]))
4329
return match.groups()[0]
4332
def commonsuffix(new, old):
4333
match = commonprefix(new[-1::-1], old[-1::-1])
4335
return match[-1::-1]
4337
def matchregexp(new, old):
4338
if re.search('\{.*(\,.*)*\}', old):
4341
# if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old):
4343
# new_reg = convert_regexp(new)
4344
# old_reg = convert_regexp(old)
4346
# pref = commonprefix(new, old)
4348
# if convert_regexp('(*,**)$') in pref:
4349
# pref = pref.replace(convert_regexp('(*,**)$'), '')
4350
# new = new.replace(pref, '', 1)
4351
# old = old.replace(pref, '', 1)
4353
# suff = commonsuffix(new, old)
4356
new_reg = convert_regexp(new)
4357
if re.search(new_reg, old):
4362
######Initialisations######
4364
conf = apparmor.config.Config('ini', CONFDIR)
4365
cfg = conf.read_config('logprof.conf')
4367
#print(cfg['settings'])
4368
#if 'default_owner_prompt' in cfg['settings']:
4369
if cfg['settings'].get('default_owner_prompt', False):
4370
cfg['settings']['default_owner_prompt'] = ''
4372
profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d'
4373
if not os.path.isdir(profile_dir):
4374
raise AppArmorException('Can\'t find AppArmor profiles')
4376
extra_profile_dir = conf.find_first_dir(cfg['settings']['inactive_profiledir']) or '/etc/apparmor/profiles/extras/'
4378
parser = conf.find_first_file(cfg['settings']['parser']) or '/sbin/apparmor_parser'
4379
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
4380
raise AppArmorException('Can\'t find apparmor_parser')
4382
filename = conf.find_first_file(cfg['settings']['logfiles']) or '/var/log/syslog'
4383
if not os.path.isfile(filename):
4384
raise AppArmorException('Can\'t find system log.')
4386
ldd = conf.find_first_file(cfg['settings']['ldd']) or '/usr/bin/ldd'
4387
if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK):
4388
raise AppArmorException('Can\'t find ldd')
4390
logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger'
4391
if not os.path.isfile(logger) or not os.access(logger, os.EX_OK):
4392
raise AppArmorException('Can\'t find logger')