#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;
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;
unsigned int ino;
int pid;
int fd;
- char process[0];
+ char *process;
+ char *process_ctx;
+ char *socket_ctx;
};
#define USER_ENT_HASH_SIZE 256
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/";
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] != '/')
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 (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:[";
char lnk[64];
int fd;
ssize_t link_len;
+ char tmp[1024];
if (sscanf(d1->d_name, "%d%c", &fd, &crap) != 1)
continue;
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;
}
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);
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);
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) {
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");
}
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) {
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;
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);
}
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);
}
" -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"
{ "filter", 1, 0, 'F' },
{ "version", 0, 0, 'V' },
{ "help", 0, 0, 'h' },
+ { "context", 0, 0, 'Z' },
+ { "contexts", 0, 0, 'z' },
{ 0 }
};
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':
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();
tcp_show(¤t_filter, IPPROTO_TCP);
if (current_filter.dbs & (1<<DCCP_DB))
tcp_show(¤t_filter, IPPROTO_DCCP);
+
+ if (show_users || show_proc_ctx || show_sock_ctx)
+ user_ent_destroy();
+
return 0;
}