[apparmor] [patch 1/4] utils/aa-unconfined: fix netstat usage, use ss(8) by default
Steve Beattie
steve at nxnw.org
Fri Dec 30 07:24:55 UTC 2016
It was reported[1] that converting the netstat command to examine
processes bound to ipv6 addresses broke on OpenSUSE due to the version
of nettools not supporting the short -4 -6 arguments.
This patch switches to use the ss(8) utility from iproute2 by default
(if ss is found) as netstat/net-tools is deprecated. Unfortunately,
ss's '--family' argument does not accept multiple families, nor
does passing '--family' multiple times with different arguments work
either[2], so aa-unconfined invokes ss multiple times to gather the
different socket families.
It also fixes the invocation of netstat to use the "--protocol
inet,inet6" arguments instead, which should return the same results
as the short options.
This patch provides command line arguments to manually switch using
one tool or the other, as well as converting the invocations of ss
and netstat to not use a shell, and documents these options in the
aa-unconfined man page.
[1] Was a bug filed for this?
[2] In fact, the version of ss/iproute2 in Ubuntu 14.04 LTS does not
restrict the listings to network sockets when 'ss -nlp --family inet'
is invoked.
Signed-off-by: Steve Beattie <steve at nxnw.org>
---
utils/aa-unconfined | 74 +++++++++++++++++++++++++++++++++++++++---------
utils/aa-unconfined.pod | 25 ++++++++++++----
2 files changed, 81 insertions(+), 18 deletions(-)
Index: b/utils/aa-unconfined
===================================================================
--- a/utils/aa-unconfined
+++ b/utils/aa-unconfined
@@ -1,6 +1,7 @@
#! /usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592 at gmail.com>
+# Copyright (C) 2016 Canonical, Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -15,6 +16,7 @@
import argparse
import os
import re
+import subprocess
import sys
import apparmor.aa as aa
@@ -31,6 +33,9 @@ _ = init_translation()
parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports"))
parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc"))
+bin_group = parser.add_mutually_exclusive_group()
+bin_group.add_argument("--with-ss", action='store_true', help=_("use ss(8) to find listening processes (default)"))
+bin_group.add_argument("--with-netstat", action='store_true', help=_("use netstat(8) to find listening processes"))
args = parser.parse_args()
paranoid = args.paranoid
@@ -39,26 +44,69 @@ aa_mountpoint = aa.check_for_apparmor()
if not aa_mountpoint:
raise aa.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again."))
-pids = []
-if paranoid:
- pids = list(filter(lambda x: re.search(r"^\d+$", x), aa.get_subdirectories("/proc")))
-else:
- regex_tcp_udp = re.compile(r"^(tcp|udp|raw)6?\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\d+|\s+)\s+(\d+)\/(\S+)")
- import subprocess
+
+def get_all_pids():
+ '''Return a set of all pids via walking /proc'''
+ return set(filter(lambda x: re.search(r"^\d+$", x), aa.get_subdirectories("/proc")))
+
+
+def get_pids_ss():
+ '''Get a set of pids listening on network sockets via ss(8)'''
+ regex_lines = re.compile(r"^(tcp|udp|raw|p_dgr)\s.+\s+users:(?P<users>\(\(.*\)\))$")
+ regex_users_pids = re.compile(r'(\("[^"]+",(pid=)?(\d+),[^)]+\))')
+
+ pids = set()
+ my_env = os.environ.copy()
+ my_env['LANG'] = 'C'
+ my_env['PATH'] = '/bin:/usr/bin:/sbin:/usr/sbin'
+ for family in ['inet', 'inet6', 'link']:
+ cmd = ['ss', '-nlp', '--family', family]
+ if sys.version_info < (3, 0):
+ output = subprocess.check_output(cmd, shell=False, env=my_env).split("\n")
+ else:
+ # Python3 needs to translate a stream of bytes to string with specified encoding
+ output = str(subprocess.check_output(cmd, shell=False, env=my_env), encoding='utf8').split("\n")
+
+ for line in output:
+ match = regex_lines.search(line.strip())
+ if match:
+ users = match.group('users')
+ for (_, _, pid) in regex_users_pids.findall(users):
+ pids.add(pid)
+ return pids
+
+
+def get_pids_netstat():
+ '''Get a set of pids listening on network sockets via netstat(8)'''
+ regex_tcp_udp = re.compile(r"^(tcp|udp|raw)6?\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\d+|\s+)\s+(?P<pid>\d+)\/(\S+)")
+
+ cmd = ['netstat', '-nlp', '--protocol', 'inet,inet6']
+ my_env = os.environ.copy()
+ my_env['LANG'] = 'C'
+ my_env['PATH'] = '/bin:/usr/bin:/sbin:/usr/sbin'
if sys.version_info < (3, 0):
- output = subprocess.check_output("LANG=C netstat -nlp46", shell=True).split("\n")
+ output = subprocess.check_output(cmd, shell=False, env=my_env).split("\n")
else:
- #Python3 needs to translate a stream of bytes to string with specified encoding
- output = str(subprocess.check_output("LANG=C netstat -nlp46", shell=True), encoding='utf8').split("\n")
+ # Python3 needs to translate a stream of bytes to string with specified encoding
+ output = str(subprocess.check_output(cmd, shell=False, env=my_env), encoding='utf8').split("\n")
+ pids = set()
for line in output:
match = regex_tcp_udp.search(line)
if match:
- pids.append(match.groups()[4])
-# We can safely remove duplicate pid's?
-pids = list(map(int, set(pids)))
+ pids.add(match.group('pid'))
+ return pids
+
+
+pids = set()
+if paranoid:
+ pids = get_all_pids()
+elif args.with_ss or (not args.with_netstat and (os.path.exists('/bin/ss') or os.path.exists('/usr/bin/ss'))):
+ pids = get_pids_ss()
+else:
+ pids = get_pids_netstat()
-for pid in sorted(pids):
+for pid in sorted(map(int, pids)):
try:
prog = os.readlink("/proc/%s/exe"%pid)
except OSError:
Index: b/utils/aa-unconfined.pod
===================================================================
--- a/utils/aa-unconfined.pod
+++ b/utils/aa-unconfined.pod
@@ -27,14 +27,29 @@ not have AppArmor profiles loaded
=head1 SYNOPSIS
-B<aa-unconfined [I<--paranoid>]>
+B<aa-unconfined [I<--paranoid>] [I<--with-ss> | I<--with-netstat>]>
=head1 OPTIONS
-B<--paranoid>
+=over 4
- Displays all processes from F</proc> filesystem with tcp or udp ports
- that do not have AppArmor profiles loaded.
+=item B<--paranoid>
+
+Displays all processes from F</proc> filesystem with tcp or udp ports
+that do not have AppArmor profiles loaded.
+
+=item B<--with-ss>
+
+Use the ss(8) command to find processes listening on network sockets
+(the default).
+
+=item B<--with-netstat>
+
+Use the netstat(8) command to find processes listening on network
+sockets. This is also what aa-unconfined will fall back to when ss(8)
+is not available.
+
+=back
=head1 DESCRIPTION
@@ -58,7 +73,7 @@ L<https://bugs.launchpad.net/apparmor/+f
=head1 SEE ALSO
-netstat(8), apparmor(7), apparmor.d(5), aa_change_hat(2), and
+ss(8), netstat(8), apparmor(7), apparmor.d(5), aa_change_hat(2), and
L<http://wiki.apparmor.net>.
=cut
More information about the AppArmor
mailing list