ss: Add support for retrieving SELinux contexts

The process SELinux contexts can be added to the output using the -Z
option. Using the -z option will show the process and socket contexts (see
the man page for details).
For netlink sockets: if valid process show process context, if pid = 0
show kernel initial context, if unknown show "unavailable".

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
This commit is contained in:
Richard Haines 2014-03-07 10:36:52 +00:00 committed by Stephen Hemminger
parent 81ebcb2ae9
commit 116ac9270b
4 changed files with 346 additions and 55 deletions

15
configure vendored
View File

@ -231,6 +231,18 @@ EOF
rm -f $TMPDIR/ipsettest.c $TMPDIR/ipsettest
}
check_selinux()
# SELinux is a compile time option in the ss utility
{
if ${PKG_CONFIG} libselinux --exists
then
echo "HAVE_SELINUX:=y" >>Config
echo "yes"
else
echo "no"
fi
}
echo "# Generated config based on" $INCLUDE >Config
check_toolchain
@ -253,3 +265,6 @@ check_ipt_lib_dir
echo -n "libc has setns: "
check_setns
echo -n "SELinux support: "
check_selinux

View File

@ -53,6 +53,37 @@ Print summary statistics. This option does not parse socket lists obtaining
summary from various sources. It is useful when amount of sockets is so huge
that parsing /proc/net/tcp is painful.
.TP
.B \-Z, \-\-context
As the
.B \-p
option but also shows process security context.
.sp
For
.BR netlink (7)
sockets the initiating process context is displayed as follows:
.RS
.RS
.IP "1." 4
If valid pid show the process context.
.IP "2." 4
If destination is kernel (pid = 0) show kernel initial context.
.IP "3." 4
If a unique identifier has been allocated by the kernel or netlink user,
show context as "unavailable". This will generally indicate that a
process has more than one netlink socket active.
.RE
.RE
.TP
.B \-z, \-\-contexts
As the
.B \-Z
option but also shows the socket context. The socket context is
taken from the associated inode and is not the actual socket
context held by the kernel. Sockets are typically labeled with the
context of the creating process, however the context shown will reflect
any policy role, type and/or range transition rules applied,
and is therefore a useful reference.
.TP
.B \-b, \-\-bpf
Show socket BPF filters (only administrators are allowed to get these information).
.TP
@ -103,6 +134,9 @@ Please take a look at the official documentation (Debian package iproute-doc) fo
.B ss -t -a
Display all TCP sockets.
.TP
.B ss -t -a -Z
Display all TCP sockets with process SELinux security contexts.
.TP
.B ss -u -a
Display all UDP sockets.
.TP

View File

@ -5,6 +5,11 @@ TARGETS=ss nstat ifstat rtacct arpd lnstat
include ../Config
ifeq ($(HAVE_SELINUX),y)
LDLIBS += $(shell pkg-config --libs libselinux)
CFLAGS += $(shell pkg-config --cflags libselinux) -DHAVE_SELINUX
endif
all: $(TARGETS)
ss: $(SSOBJ)

347
misc/ss.c
View File

@ -41,6 +41,34 @@
#include <linux/packet_diag.h>
#include <linux/netlink_diag.h>
#if HAVE_SELINUX
#include <selinux/selinux.h>
#else
/* Stubs for SELinux functions */
static int is_selinux_enabled(void)
{
return -1;
}
static int getpidcon(pid_t pid, char **context)
{
*context = NULL;
return -1;
}
static int getfilecon(char *path, char **context)
{
*context = NULL;
return -1;
}
static int security_get_initial_context(char *name, char **context)
{
*context = NULL;
return -1;
}
#endif
int resolve_hosts = 0;
int resolve_services = 1;
int preferred_family = AF_UNSPEC;
@ -50,6 +78,10 @@ int show_users = 0;
int show_mem = 0;
int show_tcpinfo = 0;
int show_bpf = 0;
int show_proc_ctx = 0;
int show_sock_ctx = 0;
/* If show_users & show_proc_ctx only do user_ent_hash_build() once */
int user_ent_hash_build_init = 0;
int netid_width;
int state_width;
@ -207,7 +239,9 @@ struct user_ent {
unsigned int ino;
int pid;
int fd;
char process[0];
char *process;
char *process_ctx;
char *socket_ctx;
};
#define USER_ENT_HASH_SIZE 256
@ -220,26 +254,50 @@ static int user_ent_hashfn(unsigned int ino)
return val & (USER_ENT_HASH_SIZE - 1);
}
static void user_ent_add(unsigned int ino, const char *process, int pid, int fd)
static void user_ent_add(unsigned int ino, char *process,
int pid, int fd,
char *proc_ctx,
char *sock_ctx)
{
struct user_ent *p, **pp;
int str_len;
str_len = strlen(process) + 1;
p = malloc(sizeof(struct user_ent) + str_len);
if (!p)
p = malloc(sizeof(struct user_ent));
if (!p) {
fprintf(stderr, "ss: failed to malloc buffer\n");
abort();
}
p->next = NULL;
p->ino = ino;
p->pid = pid;
p->fd = fd;
strcpy(p->process, process);
p->process = strdup(process);
p->process_ctx = strdup(proc_ctx);
p->socket_ctx = strdup(sock_ctx);
pp = &user_ent_hash[user_ent_hashfn(ino)];
p->next = *pp;
*pp = p;
}
static void user_ent_destroy(void)
{
struct user_ent *p, *p_next;
int cnt = 0;
while (cnt != USER_ENT_HASH_SIZE) {
p = user_ent_hash[cnt];
while (p) {
free(p->process);
free(p->process_ctx);
free(p->socket_ctx);
p_next = p->next;
free(p);
p = p_next;
}
cnt++;
}
}
static void user_ent_hash_build(void)
{
const char *root = getenv("PROC_ROOT") ? : "/proc/";
@ -247,6 +305,15 @@ static void user_ent_hash_build(void)
char name[1024];
int nameoff;
DIR *dir;
char *pid_context;
char *sock_context;
const char *no_ctx = "unavailable";
/* If show_users & show_proc_ctx set only do this once */
if (user_ent_hash_build_init != 0)
return;
user_ent_hash_build_init = 1;
strcpy(name, root);
if (strlen(name) == 0 || name[strlen(name)-1] != '/')
@ -261,6 +328,7 @@ static void user_ent_hash_build(void)
while ((d = readdir(dir)) != NULL) {
struct dirent *d1;
char process[16];
char *p;
int pid, pos;
DIR *dir1;
char crap;
@ -268,12 +336,16 @@ static void user_ent_hash_build(void)
if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1)
continue;
if (getpidcon(pid, &pid_context) != 0)
pid_context = strdup(no_ctx);
sprintf(name + nameoff, "%d/fd/", pid);
pos = strlen(name);
if ((dir1 = opendir(name)) == NULL)
continue;
process[0] = '\0';
p = process;
while ((d1 = readdir(dir1)) != NULL) {
const char *pattern = "socket:[";
@ -281,6 +353,7 @@ static void user_ent_hash_build(void)
char lnk[64];
int fd;
ssize_t link_len;
char tmp[1024];
if (sscanf(d1->d_name, "%d%c", &fd, &crap) != 1)
continue;
@ -297,55 +370,107 @@ static void user_ent_hash_build(void)
sscanf(lnk, "socket:[%u]", &ino);
if (process[0] == '\0') {
char tmp[1024];
snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s",
root, pid, d1->d_name);
if (getfilecon(tmp, &sock_context) <= 0)
sock_context = strdup(no_ctx);
if (*p == '\0') {
FILE *fp;
snprintf(tmp, sizeof(tmp), "%s/%d/stat", root, pid);
snprintf(tmp, sizeof(tmp), "%s/%d/stat",
root, pid);
if ((fp = fopen(tmp, "r")) != NULL) {
fscanf(fp, "%*d (%[^)])", process);
fscanf(fp, "%*d (%[^)])", p);
fclose(fp);
}
}
user_ent_add(ino, process, pid, fd);
user_ent_add(ino, p, pid, fd,
pid_context, sock_context);
free(sock_context);
}
free(pid_context);
closedir(dir1);
}
closedir(dir);
}
static int find_users(unsigned ino, char *buf, int buflen)
enum entry_types {
USERS,
PROC_CTX,
PROC_SOCK_CTX
};
#define ENTRY_BUF_SIZE 512
static int find_entry(unsigned ino, char **buf, int type)
{
struct user_ent *p;
int cnt = 0;
char *ptr;
char **new_buf = buf;
int len, new_buf_len;
int buf_used = 0;
int buf_len = 0;
if (!ino)
return 0;
p = user_ent_hash[user_ent_hashfn(ino)];
ptr = buf;
ptr = *buf = NULL;
while (p) {
if (p->ino != ino)
goto next;
if (ptr - buf >= buflen - 1)
break;
while (1) {
ptr = *buf + buf_used;
switch (type) {
case USERS:
len = snprintf(ptr, buf_len - buf_used,
"(\"%s\",pid=%d,fd=%d),",
p->process, p->pid, p->fd);
break;
case PROC_CTX:
len = snprintf(ptr, buf_len - buf_used,
"(\"%s\",pid=%d,proc_ctx=%s,fd=%d),",
p->process, p->pid,
p->process_ctx, p->fd);
break;
case PROC_SOCK_CTX:
len = snprintf(ptr, buf_len - buf_used,
"(\"%s\",pid=%d,proc_ctx=%s,fd=%d,sock_ctx=%s),",
p->process, p->pid,
p->process_ctx, p->fd,
p->socket_ctx);
break;
default:
fprintf(stderr, "ss: invalid type: %d\n", type);
abort();
}
snprintf(ptr, buflen - (ptr - buf),
"(\"%s\",%d,%d),",
p->process, p->pid, p->fd);
ptr += strlen(ptr);
if (len < 0 || len >= buf_len - buf_used) {
new_buf_len = buf_len + ENTRY_BUF_SIZE;
*new_buf = realloc(*buf, new_buf_len);
if (!new_buf) {
fprintf(stderr, "ss: failed to malloc buffer\n");
abort();
}
**buf = **new_buf;
buf_len = new_buf_len;
continue;
} else {
buf_used += len;
break;
}
}
cnt++;
next:
next:
p = p->next;
}
if (ptr != buf)
if (buf_used) {
ptr = *buf + buf_used;
ptr[-1] = '\0';
}
return cnt;
}
@ -1289,11 +1414,21 @@ static int tcp_show_line(char *line, const struct filter *f, int family)
if (s.qack&1)
printf(" bidir");
}
if (show_users) {
char ubuf[4096];
if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(s.ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(s.ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_details) {
if (s.uid)
printf(" uid:%u", (unsigned)s.uid);
@ -1527,11 +1662,22 @@ static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol)
r->idiag_retrans);
}
}
if (show_users) {
char ubuf[4096];
if (find_users(r->idiag_inode, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(r->idiag_inode, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(r->idiag_inode, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_details) {
if (r->idiag_uid)
printf(" uid:%u", (unsigned)r->idiag_uid);
@ -2016,10 +2162,20 @@ static int dgram_show_line(char *line, const struct filter *f, int family)
formatted_print(&s.local, s.lport, 0);
formatted_print(&s.remote, s.rport, 0);
if (show_users) {
char ubuf[4096];
if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(s.ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(s.ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_details) {
@ -2206,10 +2362,20 @@ static void unix_list_print(struct unixstat *list, struct filter *f)
printf("%*s %-*d %*s %-*d",
addr_width, s->name ? : "*", serv_width, s->ino,
addr_width, peer, serv_width, s->peer);
if (show_users) {
char ubuf[4096];
if (find_users(s->ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(s->ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(s->ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
printf("\n");
}
@ -2271,10 +2437,20 @@ static int unix_show_sock(struct nlmsghdr *nlh, struct filter *f)
addr_width, "*", /* FIXME */
serv_width, peer_ino);
if (show_users) {
char ubuf[4096];
if (find_users(r->udiag_ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(r->udiag_ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(r->udiag_ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_mem) {
@ -2532,11 +2708,22 @@ static int packet_show_sock(struct nlmsghdr *nlh, struct filter *f)
printf("%*s*%-*s",
addr_width, "", serv_width, "");
if (show_users) {
char ubuf[4096];
if (find_users(r->pdiag_ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(r->pdiag_ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(r->pdiag_ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_details) {
__u32 uid = 0;
@ -2727,11 +2914,22 @@ static int packet_show(struct filter *f)
printf("%*s*%-*s",
addr_width, "", serv_width, "");
if (show_users) {
char ubuf[4096];
if (find_users(ino, ubuf, sizeof(ubuf)) > 0)
printf(" users:(%s)", ubuf);
char *buf = NULL;
if (show_proc_ctx || show_sock_ctx) {
if (find_entry(ino, &buf,
(show_proc_ctx & show_sock_ctx) ?
PROC_SOCK_CTX : PROC_CTX) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
} else if (show_users) {
if (find_entry(ino, &buf, USERS) > 0) {
printf(" users:(%s)", buf);
free(buf);
}
}
if (show_details) {
printf(" ino=%u uid=%u sk=%llx", ino, uid, sk);
}
@ -2807,6 +3005,27 @@ static void netlink_show_one(struct filter *f,
addr_width, "", serv_width, "");
}
char *pid_context = NULL;
if (show_proc_ctx) {
/* The pid value will either be:
* 0 if destination kernel - show kernel initial context.
* A valid process pid - use getpidcon.
* A unique value allocated by the kernel or netlink user
* to the process - show context as "not available".
*/
if (!pid)
security_get_initial_context("kernel", &pid_context);
else if (pid > 0)
getpidcon(pid, &pid_context);
if (pid_context != NULL) {
printf("proc_ctx=%-*s ", serv_width, pid_context);
free(pid_context);
} else {
printf("proc_ctx=%-*s ", serv_width, "unavailable");
}
}
if (show_details) {
printf(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups);
}
@ -3081,6 +3300,8 @@ static void _usage(FILE *dest)
" -i, --info show internal TCP information\n"
" -s, --summary show socket usage summary\n"
" -b, --bpf show bpf filter socket information\n"
" -Z, --context display process SELinux security contexts\n"
" -z, --contexts display process and socket SELinux security contexts\n"
"\n"
" -4, --ipv4 display only IP version 4 sockets\n"
" -6, --ipv6 display only IP version 6 sockets\n"
@ -3170,6 +3391,8 @@ static const struct option long_opts[] = {
{ "filter", 1, 0, 'F' },
{ "version", 0, 0, 'V' },
{ "help", 0, 0, 'h' },
{ "context", 0, 0, 'Z' },
{ "contexts", 0, 0, 'z' },
{ 0 }
};
@ -3188,7 +3411,7 @@ int main(int argc, char *argv[])
current_filter.states = default_filter.states;
while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbf:miA:D:F:vV",
while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbf:miA:D:F:vVzZ",
long_opts, NULL)) != EOF) {
switch(ch) {
case 'n':
@ -3348,6 +3571,16 @@ int main(int argc, char *argv[])
case 'V':
printf("ss utility, iproute2-ss%s\n", SNAPSHOT);
exit(0);
case 'z':
show_sock_ctx++;
case 'Z':
if (is_selinux_enabled() <= 0) {
fprintf(stderr, "ss: SELinux is not enabled.\n");
exit(1);
}
show_proc_ctx++;
user_ent_hash_build();
break;
case 'h':
case '?':
help();
@ -3535,5 +3768,9 @@ int main(int argc, char *argv[])
tcp_show(&current_filter, IPPROTO_TCP);
if (current_filter.dbs & (1<<DCCP_DB))
tcp_show(&current_filter, IPPROTO_DCCP);
if (show_users || show_proc_ctx || show_sock_ctx)
user_ent_destroy();
return 0;
}