Message ID | 1392986040-3711-1-git-send-email-richard_c_haines@btinternet.com |
---|---|
State | Changes Requested, archived |
Delegated to: | stephen hemminger |
Headers | show |
On Fri, 21 Feb 2014 12:34:00 +0000 Richard Haines <richard_c_haines@btinternet.com> wrote: > 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 "not available". > > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> > --- > configure | 15 +++ > man/man8/ss.8 | 34 ++++++ > misc/Makefile | 12 ++ > misc/ss.c | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- > 4 files changed, 386 insertions(+), 50 deletions(-) > > diff --git a/configure b/configure > index da01c19..d5170f0 100755 > --- a/configure > +++ b/configure > @@ -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 > diff --git a/man/man8/ss.8 b/man/man8/ss.8 > index 807d9dc..d6e43ba 100644 > --- a/man/man8/ss.8 > +++ b/man/man8/ss.8 > @@ -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 "not available". 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 > diff --git a/misc/Makefile b/misc/Makefile > index a59ff87..d1f295b 100644 > --- a/misc/Makefile > +++ b/misc/Makefile > @@ -8,6 +8,18 @@ include ../Config > all: $(TARGETS) > > ss: $(SSOBJ) > +ifeq ($(HAVE_SELINUX),y) > + $(CC) $(LDFLAGS) -o $@ $(SSOBJ) $(LDLIBS) $(shell pkg-config --libs libselinux) > +else > + $(CC) $(LDFLAGS) -o $@ $(SSOBJ) $(LDLIBS) > +endif > + > +ss.o: ss.c > +ifeq ($(HAVE_SELINUX),y) > + $(CC) $(CFLAGS) $(shell pkg-config --cflags libselinux) -DHAVE_SELINUX -c $+ > +else > + $(CC) $(CFLAGS) -c $+ > +endif > > nstat: nstat.c > $(CC) $(CFLAGS) $(LDFLAGS) -o nstat nstat.c -lm > diff --git a/misc/ss.c b/misc/ss.c > index ce6a0a8..c1b1617 100644 > --- a/misc/ss.c > +++ b/misc/ss.c > @@ -40,6 +40,9 @@ > #include <linux/filter.h> > #include <linux/packet_diag.h> > #include <linux/netlink_diag.h> > +#if HAVE_SELINUX > +#include <selinux/selinux.h> > +#endif > > int resolve_hosts = 0; > int resolve_services = 1; > @@ -50,6 +53,12 @@ int show_users = 0; > int show_mem = 0; > int show_tcpinfo = 0; > int show_bpf = 0; > +#if HAVE_SELINUX > +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; > +#endif > > int netid_width; > int state_width; > @@ -207,7 +216,11 @@ struct user_ent { > unsigned int ino; > int pid; > int fd; > - char process[0]; > + char *process; > +#if HAVE_SELINUX > + char *process_ctx; > + char *socket_ctx; > +#endif > }; > > #define USER_ENT_HASH_SIZE 256 > @@ -220,26 +233,58 @@ 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) > +#if HAVE_SELINUX > +static void user_ent_add(unsigned int ino, char *process, > + int pid, int fd, > + char *proc_ctx, > + char *sock_ctx) > +#else > +static void user_ent_add(unsigned int ino, char *process, int pid, int fd) > +#endif > { > 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); > +#if HAVE_SELINUX > + p->process_ctx = strdup(proc_ctx); > + p->socket_ctx = strdup(sock_ctx); > +#endif > > 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); > +#if HAVE_SELINUX > + freecon(p->process_ctx); > + freecon(p->socket_ctx); > +#endif > + 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 +292,17 @@ static void user_ent_hash_build(void) > char name[1024]; > int nameoff; > DIR *dir; > +#if HAVE_SELINUX > + char *pid_context; > + char *sock_context; > + char *no_ctx = "not available"; > + > + /* If show_users and show_proc_ctx set only do this once */ > + if (user_ent_hash_build_init != 0) > + return; > + > + user_ent_hash_build_init = 1; > +#endif > > strcpy(name, root); > if (strlen(name) == 0 || name[strlen(name)-1] != '/') > @@ -261,19 +317,24 @@ 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; > > if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1) > continue; > - > +#if HAVE_SELINUX > + if (getpidcon(pid, &pid_context) != 0) > + pid_context = strdup(no_ctx); > +#endif > 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 +342,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; > @@ -296,56 +358,122 @@ static void user_ent_hash_build(void) > continue; > > sscanf(lnk, "socket:[%u]", &ino); > - > - if (process[0] == '\0') { > - char tmp[1024]; > +#if HAVE_SELINUX > + snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s", > + root, pid, d1->d_name); > + > + if (getfilecon(tmp, &sock_context) < 0) > + sock_context = strdup(no_ctx); > +#endif > + 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); > +#if HAVE_SELINUX > + user_ent_add(ino, p, pid, fd, > + pid_context, sock_context); > + freecon(sock_context); > } > + freecon(pid_context); > closedir(dir1); > +#else > + user_ent_add(ino, p, pid, fd); > + } > + closedir(dir1); > +#endif > } > closedir(dir); > } > > -static int find_users(unsigned ino, char *buf, int buflen) > +#if HAVE_SELINUX > +enum entry_types { > + USERS, > + PROC_CTX, > + PROC_SOCK_CTX > +}; > +#else > +enum entry_types { > + USERS > +}; > +#endif > + > +#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; > +#if HAVE_SELINUX > + 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; > +#endif > + 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 +1417,25 @@ static int tcp_show_line(char *line, const struct filter *f, int family) > if (s.qack&1) > printf(" bidir"); > } > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + 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 +1669,25 @@ static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol) > r->idiag_retrans); > } > } > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(r->idiag_inode, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + 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 +2172,23 @@ 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); > > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + if (find_entry(s.ino, &buf, USERS) > 0) { > + printf(" users:(%s)", buf); > + free(buf); > + } > } > > if (show_details) { > @@ -2206,10 +2375,23 @@ 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); > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(s->ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + if (find_entry(s->ino, &buf, USERS) > 0) { > + printf(" users:(%s)", buf); > + free(buf); > + } > } > printf("\n"); > } > @@ -2271,10 +2453,23 @@ static int unix_show_sock(struct nlmsghdr *nlh, struct filter *f) > addr_width, "*", /* FIXME */ > serv_width, peer_ino); > > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(r->udiag_ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + if (find_entry(r->udiag_ino, &buf, USERS) > 0) { > + printf(" users:(%s)", buf); > + free(buf); > + } > } > > if (show_mem) { > @@ -2532,11 +2727,25 @@ static int packet_show_sock(struct nlmsghdr *nlh, struct filter *f) > printf("%*s*%-*s", > addr_width, "", serv_width, ""); > > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(r->pdiag_ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + if (find_entry(r->pdiag_ino, &buf, USERS) > 0) { > + printf(" users:(%s)", buf); > + free(buf); > + } > } > + > if (show_details) { > __u32 uid = 0; > > @@ -2727,11 +2936,25 @@ static int packet_show(struct filter *f) > printf("%*s*%-*s", > addr_width, "", serv_width, ""); > > + char *buf = NULL; > +#if HAVE_SELINUX > + 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) { > +#else > if (show_users) { > - char ubuf[4096]; > - if (find_users(ino, ubuf, sizeof(ubuf)) > 0) > - printf(" users:(%s)", ubuf); > +#endif > + 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); > } > @@ -2806,6 +3029,29 @@ static void netlink_show_one(struct filter *f, > printf("%*s*%-*s", > addr_width, "", serv_width, ""); > } > +#if HAVE_SELINUX > + 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); > + freecon(pid_context); > + } else { > + printf("proc_ctx=%-*s ", serv_width, "not available"); > + } > + } > +# endif > > if (show_details) { > printf(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups); > @@ -3081,6 +3327,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 +3418,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 +3438,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 +3598,23 @@ int main(int argc, char *argv[]) > case 'V': > printf("ss utility, iproute2-ss%s\n", SNAPSHOT); > exit(0); > + case 'z': > +#if HAVE_SELINUX > + show_sock_ctx++; > +#endif > + case 'Z': > +#if HAVE_SELINUX > + if (is_selinux_enabled() <= 0) { > + fprintf(stderr, "ss: SELinux is not enabled.\n"); > + exit(1); > + } > + show_proc_ctx++; > + user_ent_hash_build(); > +#else > + fprintf(stderr, "ss: version does not support SELinux.\n"); > + exit(1); > +#endif > + break; > case 'h': > case '?': > help(); > @@ -3535,5 +3802,13 @@ int main(int argc, char *argv[]) > tcp_show(¤t_filter, IPPROTO_TCP); > if (current_filter.dbs & (1<<DCCP_DB)) > tcp_show(¤t_filter, IPPROTO_DCCP); > + > +#if HAVE_SELINUX > + if (show_users || show_proc_ctx || show_sock_ctx) > +#else > + if (show_users) > +#endif > + user_ent_destroy(); > + > return 0; > } This is too messy to merge right now, it creates multiple paths in existing code. Can you do this without all the ifdef code. Just make a small set of functions that return false if SELINUX not present. for example, just make a stub for is_selinux_enabled() which always returns false if the library is not available. -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/configure b/configure index da01c19..d5170f0 100755 --- a/configure +++ b/configure @@ -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 diff --git a/man/man8/ss.8 b/man/man8/ss.8 index 807d9dc..d6e43ba 100644 --- a/man/man8/ss.8 +++ b/man/man8/ss.8 @@ -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 "not available". 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 diff --git a/misc/Makefile b/misc/Makefile index a59ff87..d1f295b 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -8,6 +8,18 @@ include ../Config all: $(TARGETS) ss: $(SSOBJ) +ifeq ($(HAVE_SELINUX),y) + $(CC) $(LDFLAGS) -o $@ $(SSOBJ) $(LDLIBS) $(shell pkg-config --libs libselinux) +else + $(CC) $(LDFLAGS) -o $@ $(SSOBJ) $(LDLIBS) +endif + +ss.o: ss.c +ifeq ($(HAVE_SELINUX),y) + $(CC) $(CFLAGS) $(shell pkg-config --cflags libselinux) -DHAVE_SELINUX -c $+ +else + $(CC) $(CFLAGS) -c $+ +endif nstat: nstat.c $(CC) $(CFLAGS) $(LDFLAGS) -o nstat nstat.c -lm diff --git a/misc/ss.c b/misc/ss.c index ce6a0a8..c1b1617 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -40,6 +40,9 @@ #include <linux/filter.h> #include <linux/packet_diag.h> #include <linux/netlink_diag.h> +#if HAVE_SELINUX +#include <selinux/selinux.h> +#endif int resolve_hosts = 0; int resolve_services = 1; @@ -50,6 +53,12 @@ int show_users = 0; int show_mem = 0; int show_tcpinfo = 0; int show_bpf = 0; +#if HAVE_SELINUX +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; +#endif int netid_width; int state_width; @@ -207,7 +216,11 @@ struct user_ent { unsigned int ino; int pid; int fd; - char process[0]; + char *process; +#if HAVE_SELINUX + char *process_ctx; + char *socket_ctx; +#endif }; #define USER_ENT_HASH_SIZE 256 @@ -220,26 +233,58 @@ 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) +#if HAVE_SELINUX +static void user_ent_add(unsigned int ino, char *process, + int pid, int fd, + char *proc_ctx, + char *sock_ctx) +#else +static void user_ent_add(unsigned int ino, char *process, int pid, int fd) +#endif { 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); +#if HAVE_SELINUX + p->process_ctx = strdup(proc_ctx); + p->socket_ctx = strdup(sock_ctx); +#endif 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); +#if HAVE_SELINUX + freecon(p->process_ctx); + freecon(p->socket_ctx); +#endif + 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 +292,17 @@ static void user_ent_hash_build(void) char name[1024]; int nameoff; DIR *dir; +#if HAVE_SELINUX + char *pid_context; + char *sock_context; + char *no_ctx = "not available"; + + /* If show_users and show_proc_ctx set only do this once */ + if (user_ent_hash_build_init != 0) + return; + + user_ent_hash_build_init = 1; +#endif strcpy(name, root); if (strlen(name) == 0 || name[strlen(name)-1] != '/') @@ -261,19 +317,24 @@ 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; if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1) continue; - +#if HAVE_SELINUX + if (getpidcon(pid, &pid_context) != 0) + pid_context = strdup(no_ctx); +#endif 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 +342,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; @@ -296,56 +358,122 @@ static void user_ent_hash_build(void) continue; sscanf(lnk, "socket:[%u]", &ino); - - if (process[0] == '\0') { - char tmp[1024]; +#if HAVE_SELINUX + snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s", + root, pid, d1->d_name); + + if (getfilecon(tmp, &sock_context) < 0) + sock_context = strdup(no_ctx); +#endif + 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); +#if HAVE_SELINUX + user_ent_add(ino, p, pid, fd, + pid_context, sock_context); + freecon(sock_context); } + freecon(pid_context); closedir(dir1); +#else + user_ent_add(ino, p, pid, fd); + } + closedir(dir1); +#endif } closedir(dir); } -static int find_users(unsigned ino, char *buf, int buflen) +#if HAVE_SELINUX +enum entry_types { + USERS, + PROC_CTX, + PROC_SOCK_CTX +}; +#else +enum entry_types { + USERS +}; +#endif + +#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; +#if HAVE_SELINUX + 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; +#endif + 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 +1417,25 @@ static int tcp_show_line(char *line, const struct filter *f, int family) if (s.qack&1) printf(" bidir"); } + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + 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 +1669,25 @@ static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol) r->idiag_retrans); } } + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(r->idiag_inode, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + 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 +2172,23 @@ 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); + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(s.ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + if (find_entry(s.ino, &buf, USERS) > 0) { + printf(" users:(%s)", buf); + free(buf); + } } if (show_details) { @@ -2206,10 +2375,23 @@ 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); + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(s->ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + if (find_entry(s->ino, &buf, USERS) > 0) { + printf(" users:(%s)", buf); + free(buf); + } } printf("\n"); } @@ -2271,10 +2453,23 @@ static int unix_show_sock(struct nlmsghdr *nlh, struct filter *f) addr_width, "*", /* FIXME */ serv_width, peer_ino); + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(r->udiag_ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + if (find_entry(r->udiag_ino, &buf, USERS) > 0) { + printf(" users:(%s)", buf); + free(buf); + } } if (show_mem) { @@ -2532,11 +2727,25 @@ static int packet_show_sock(struct nlmsghdr *nlh, struct filter *f) printf("%*s*%-*s", addr_width, "", serv_width, ""); + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(r->pdiag_ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + if (find_entry(r->pdiag_ino, &buf, USERS) > 0) { + printf(" users:(%s)", buf); + free(buf); + } } + if (show_details) { __u32 uid = 0; @@ -2727,11 +2936,25 @@ static int packet_show(struct filter *f) printf("%*s*%-*s", addr_width, "", serv_width, ""); + char *buf = NULL; +#if HAVE_SELINUX + 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) { +#else if (show_users) { - char ubuf[4096]; - if (find_users(ino, ubuf, sizeof(ubuf)) > 0) - printf(" users:(%s)", ubuf); +#endif + 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); } @@ -2806,6 +3029,29 @@ static void netlink_show_one(struct filter *f, printf("%*s*%-*s", addr_width, "", serv_width, ""); } +#if HAVE_SELINUX + 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); + freecon(pid_context); + } else { + printf("proc_ctx=%-*s ", serv_width, "not available"); + } + } +# endif if (show_details) { printf(" sk=%llx cb=%llx groups=0x%08x", sk, cb, groups); @@ -3081,6 +3327,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 +3418,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 +3438,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 +3598,23 @@ int main(int argc, char *argv[]) case 'V': printf("ss utility, iproute2-ss%s\n", SNAPSHOT); exit(0); + case 'z': +#if HAVE_SELINUX + show_sock_ctx++; +#endif + case 'Z': +#if HAVE_SELINUX + if (is_selinux_enabled() <= 0) { + fprintf(stderr, "ss: SELinux is not enabled.\n"); + exit(1); + } + show_proc_ctx++; + user_ent_hash_build(); +#else + fprintf(stderr, "ss: version does not support SELinux.\n"); + exit(1); +#endif + break; case 'h': case '?': help(); @@ -3535,5 +3802,13 @@ int main(int argc, char *argv[]) tcp_show(¤t_filter, IPPROTO_TCP); if (current_filter.dbs & (1<<DCCP_DB)) tcp_show(¤t_filter, IPPROTO_DCCP); + +#if HAVE_SELINUX + if (show_users || show_proc_ctx || show_sock_ctx) +#else + if (show_users) +#endif + user_ent_destroy(); + return 0; }
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 "not available". Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> --- configure | 15 +++ man/man8/ss.8 | 34 ++++++ misc/Makefile | 12 ++ misc/ss.c | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 386 insertions(+), 50 deletions(-)