[SCRIPT] avctree

Lee Kok Seng kokseng at ieee.org
Tue May 15 04:13:50 UTC 2007


Hello,

While diagnosing avc messages, I found the log message too spread-out  
to form a mental picture,
since the lack of some rules often result in several domain domains  
barking. This reminds me of
unforgiving Ada compilers spilling out loads of messages.   I did not  
want to use audit2allow
too quickly until I understood what the machine is not happy with.  
So, I needed a message
format that let me to that.

Here is a simple perl script to parse log files for avc denial  
messages, index, sort them, and print them
in tree (single depth) view, which I hastily put together last  
night.  I hope it will help others as it did
for me. Please feel free to modify it for your own use.

You can index the message by any key, for example scontext, tcontext,  
action, name and etc. You can
also specify the log files to parse.  By default, the script trim the  
context string of the _u, _r and _t, which
are good for rule readability in source files, but clutter diagnostic  
print out. However, if this bothers you,
disable trimming by --trim=no option.   To get help and condition of  
usage,  avctree --help. It is
best to pipe the output to less so that you can navigate.

A typical partial print out is as follows (this one indexed by  
tcontext):

#  
------------------------------------------------------------------------ 
--------[tcontext]
|
+-[root-object-default      ]
| +<- system-system-initrc_su      su(1753) : dir : search : home :  
dm-0 : 49182
|
+-[root-object-selinux_config]
| +<- root-system-semanage         semodule(3584) : dir : rename :  
active : sdb1 : 49833
|
+-[root-object-user_home    ]
| +<- root-system-semanage         semodule(10359) : lnk_file :  
read : targeted : sdb1 : 98758
| +<- root-system-semanage         semodule(11006) : lnk_file :  
read : policy : sdb1 : 98764
| +<- root-system-semanage         semodule(3584) : lnk_file : read :  
targeted : sdb1 : 98758
|
+-[system-object-default    ]
| +<- system-system-initrc_su      su(1753) : dir : search : / :  
dm-0 : 2
| +<- system-system-hald           hald(1958) : dir : getattr : / :  
dm-0 : 2
|


No PP module for this script yet.  This script use basic Perl  
features, so, as along you have base Perl package installed, it  
should work.
Be happy to hear any comments or suggestion for improving this.

/ks

#------------------------------------------- cut here  
-----------------------------------------
#!/usr/bin/perl -w
sub lmsg
{
	print <<LMSG;;
# Copyright (C) 2007,  LEE, "Kok Seng" (kokseng at ieee dot org)
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License as
#    published by the Free Software Foundation; either version 2 of
#    the License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
#                                        02111-1307  USA
#
LMSG
}
my $version='1.0.0';
use strict;
use warnings;
my $thisScript = $0;
$thisScript =~ s#([^/]+[/]|)(\w+)#$2#;
#  
------------------------------------------------------------------------ 
----------------------
use Getopt::Long;
sub usage
{
	lmsg;
	print "Usage:\n";
print <<USAGETXT;
	Utility to format avc messages for readability
	----------------------------------------------------------------------- 
---------------------
	$thisScript  [[options] ...]

	Options:
         --log=[ all | file,...] : List log files to parse, delimited  
by comma.
                                       no argument or all => /var/log/ 
messages, /var/log/kernel
         --tags                  : Show time and audit tags
         --key=key,...           : List messages indexed-sorted by  
specified key
								      no argument or all => all keys
                                       Not specified =>  
scontext,tcontext,action,comm,name
         --trim=yes|no|1|0       : Trim context string. Default: yes
		--help
	----------------------------------------------------------------------- 
---------------------
     Examples:
     a.  $thisScript --key=scontext
            Print avc messages indexed-sorted by source context.
     b.  $thisScript --key=tcontext
            Print avc messages indexed-sorted by target context.
     c.  $thisScript --key=comm
            Print avc messages indexed-sorted by command executed.
     d.  $thisScript --key=name
            Print avc messages indexed-sorted by target object's name.
     e.  $thisScript --key=all  or $thisScript --key
            Print avc messages indexed-sorted by all keys.
     f.  $thisScript
            Print avc messages indexed-sorted by scontext, tcontext,  
comm, name (default)
     g.  $thisScript --trim=no
            Print avc messages without trimming context string.
     h.  $thisScript --log=/var/log/messages,/var/log/messages.1,/var/ 
log/messages.2
            Print avc messages from log files listed (delimited by  
comma).
     i.  $thisScript --tags
           Print avc messages, including in each message the log time  
tag and audit tag.
USAGETXT
	exit -1;
}
#
my $logARG;												# Log files to parse
my $tagsARG;											# Show time and audit tags
my $catARG;												# Categories to print
my $helpARG;											# Help
my $trimARG;											# Trim context string for readability

usage(), exit unless GetOptions(
	'log:s'				=>	\$logARG,
	'tags!'				=>	\$tagsARG,
	'key:s'				=>	\$catARG,
	'trim:s'			=>  \$trimARG,
	'help!'				=>	\$helpARG
);
usage() if (defined($helpARG));
##  
------------------------------------------------------------------------ 
----------------------
## Option: skip tags
my $skiptags = defined($tagsARG)?0:1;
## Option: log files
my @logOPT	= grep -d $_, split /,|\n|\r/, $logARG if (defined($logARG));
@logOPT = ('/var/log/messages','/var/log/kernel','/var/log/debug')
	if (defined($logARG) && ((!scalar @logOPT) || grep /all/, @logOPT));
@logOPT = ('/var/log/kernel') if (!scalar @logOPT && -d '/var/log/ 
kernel');
@logOPT = ('/var/log/messages') if (!scalar @logOPT);
## Option: Category
my @catOPT	= split /,|\n|\r/, $catARG if (defined($catARG));
my @catDEF = ('scontext','tcontext','comm','name');
## Option: Trim
my $trimOPT	=  defined($trimARG) ? ($trimARG =~ /no|0|/i ? 0 : 1  ) : 1;
##  
------------------------------------------------------------------------ 
----------------------
#
## Regular expression for parsing avc's 'denied' messages
my $avcRE = qr/^(\w{3}\s+\d{2}\s+\d{2}:\d{2}:\d{2})[\s\w]+:\s*audit\ 
(([\d.:]+)\)\s*:\s*avc\s*:\s*denied\s+\{\s+(\w+)\s+}\s+for\s+(.*)/;
## Holds indexed avc message records
my %avc;								
##  
------------------------------------------------------------------------ 
----------------------
## contextFMT
# 		Format context string for readability
sub contextFMT
{
	my $ctxt = shift;
	my ($u,$r,$t,$l) = split /:/, $ctxt;
	$u =~ s/(.*)_./$1/;
	$r =~ s/(.*)_./$1/;
	$t =~ s/(.*)_./$1/;
	return $u . '-' . $r . '-'  . $t;
}
##  
------------------------------------------------------------------------ 
----------------------
## readLOG  log-file-name
# 		Reads the specified log file
sub readLOG
{
	my $avc = shift;
	my $logfile = shift;
	my $logsn = ($logfile =~ /.*\/(.*)$/)[0];
	my $tmax = defined($avc->{'_tcontext_max_'})?$avc-> 
{'_tcontext_max_'}:0;
	my $smax = defined($avc->{'_scontext_max_'})?$avc-> 
{'_scontext_max_'}:0;

	open LOGF, '<' . $logfile || die "Cannot open input file: $logfile";

	while (<LOGF>) {
		s/\r|\n//g;
		next if (!$_);
		next if (!/\s+avc:\s+/);
		my ($timetag, $audit, $action, $detail)  = ($_ =~ /$avcRE/);
		next if (!defined($action)||!defined($detail)||!defined($timetag)||! 
defined($audit));

		# okay, we have a avc 'denied' message
		my %this;		# this hash will keep the message's key=value
		my @fields = split /\s|\r|\n/, $detail;
		foreach  (@fields) {
			next if (!$_);
			my ($key,$val) = split/=/;
			next if (!$key||!$val);
			$val =~ s/[\"\']*([^\"\']*)[\"\']*/$1/;
			$this{"$key"} = $val;
		}
		next if (!defined($this{'scontext'}) || !defined($this{'tcontext'}));

		$this{'action'} = $action;
		$this{'timetag'} = $timetag;
		$this{'audit'} = $audit;
		$this{'file'}= $logsn;
		if ($trimOPT) {
			$this{'scontext'} = contextFMT($this{'scontext'});
			$this{'tcontext'} = contextFMT($this{'tcontext'});
		}
		$smax = length($this{'scontext'})  if ($smax < length($this 
{'scontext'}));
		$tmax = length($this{'tcontext'})  if ($tmax < length($this 
{'tcontext'}));

		# Okay, let's index the records with various keys
		foreach (keys %this) {
			next if (/audit|timetag|file/);
			$avc->{$_} = {} if (!defined($avc->{$_}));
			$avc->{$_}->{$this{$_}} = [()] if (!defined($avc->{$_}->{$this 
{$_}}));
			push @{$avc->{$_}->{$this{$_}}}, \%this;
		}


	}
	close LOGF;
	$avc->{'_scontext_max_'} = $smax;
	$avc->{'_tcontext_max_'} = $tmax;
}
##  
------------------------------------------------------------------------ 
----------------------
##
# keyTREE key
#	Show selected key in a tree view
sub keyTREE
{
	my $avc = shift;
	my $kcat = shift;
	my $showfile = shift;
	my $hcat = $avc->{$kcat};
	my $lvl = 1;
	my $isSctx = ($kcat =~ /scontext/);
	my $isTctx = ($kcat =~ /tcontext/);

	my $smax = $isSctx ? 0 : $avc->{'_scontext_max_'};
	my $tmax = $isTctx ? 0 : $avc->{'_tcontext_max_'};

	return if (/_scontext_max_|_tcontext_max_/);
	print "\n# "; for ($_=0; $_ < 80; $_++) {print "-";}
	print "[", $kcat, "]\n";

	foreach my $kmsg (sort keys %$hcat) {
		printf "|\n+-[%-*s]\n", $smax, $hcat->{$kmsg}[0]->{$kcat};
		$lvl++;
		my $buf;
		my $i;
		my $cnt = scalar @{$hcat->{$kmsg}};
		foreach my $hmsg (@{$hcat->{$kmsg}}) {
			
			$buf .= sprintf "%s %-*s %s %-*s  %s%s(%s) : %s : %s %s%s%s\n",
					$isTctx? '+<-' : '+->',
					$smax, $isSctx?'':$hmsg->{'scontext'},
					$isTctx||$isSctx ? '' : '-+->',
					$tmax, $isTctx?'':$hmsg->{'tcontext'},
					defined($showfile)? $hmsg->{'file'}.'> ':'',
					$hmsg->{'comm'}, $hmsg->{'pid'}, $hmsg->{'tclass'},
					$hmsg->{'action'},
					defined($hmsg->{'name'})?': '.$hmsg->{'name'}:'',
					defined($hmsg->{'key'})?' : '.$hmsg->{'key'}:'',
					defined($hmsg->{'dev'})?' : '.$hmsg->{'dev'} . (defined($hmsg-> 
{'ino'})?' : '.$hmsg->{'ino'}:'') : ''
				;
			$i = $lvl; $buf  = '| ' . $buf while (--$i); print $buf; $buf = "";

			foreach my $kmsg (sort keys %$hmsg) {
				next if ($kmsg =~ /file|scontext|tcontext|comm|pid|tclass|action| 
name|dev|ino|key|$kcat/);
				next if ($skiptags && $kmsg =~ /timetag|audit/);
				$buf .= sprintf "%s=%s ", $kmsg, $hmsg->{$kmsg};
			}
			if ($buf) {
				$buf = sprintf "%*s%s\n", ($cnt==1)?$smax+$tmax+10+2:$smax+$tmax 
+10, ,"", $buf;
				$i = (--$cnt)? $lvl:$lvl-1; $buf  = '| ' . $buf while ($i--);  
print $buf; $buf = "";
			}
		}
		$lvl--;
	}
	$lvl--;
}
##  
------------------------------------------------------------------------ 
----------------------
# Parse log files
readLOG(\%avc, $_) foreach (@logOPT);
# Decide which category to print
@catOPT = (sort keys %avc)  if (defined($catARG) && (! scalar  
@catOPT) ||  grep /all/, at catOPT ) ;
@catOPT = @catDEF if (!defined($catARG));
print "\n> Copyright (C) 2007,  LEE, \"Kok Seng\" (kokseng at ieee  
dot org)";
print "\n> Notice: get help and condition of usage inforamtion  
regarding this script: $thisScript --help\n";
keyTREE(\%avc, $_,scalar @logOPT > 1?1:undef) foreach (@catOPT);
##  
------------------------------------------------------------------------ 
----------------------
# vim :ts=4:sw=4:
1;


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/fedora-selinux-list/attachments/20070515/d508a832/attachment.htm>


More information about the fedora-selinux-list mailing list