]> git.proxmox.com Git - mirror_iproute2.git/commitdiff
ss: Add support for retrieving SELinux contexts
authorRichard Haines <richard_c_haines@btinternet.com>
Fri, 7 Mar 2014 10:36:52 +0000 (10:36 +0000)
committerStephen Hemminger <stephen@networkplumber.org>
Mon, 10 Mar 2014 20:20:49 +0000 (13:20 -0700)
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>
configure
man/man8/ss.8
misc/Makefile
misc/ss.c

index da01c19b6afa37ea500b2cd45711985b18410b8e..d5170f0a865a69930500c0dbd606dd28ba36d871 100755 (executable)
--- 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
index 807d9dc9179d5c222103d8190dc3c9ff922c2a75..2a4bbc570dc426276ddd8756a9cbca408b1f9f1f 100644 (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
index a59ff87154fe8951f441739360d6122a1781484e..004bcc3af646af9a586a2fb4ce84b84687b06f38 100644 (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)
index ce6a0a8d8fa17c0e5fbdbffb8a2aaa46c13e5412..187c8524b46c65bbe090cc34bfb71ed9736eb6b0 100644 (file)
--- a/misc/ss.c
+++ b/misc/ss.c
 #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;
 }