perf trace: WIP: sockaddr syscall arg beautifier

We need to use the following BPF program together with the following
(long) 'perf trace' command line, more work is needed to do this setup
completely transparently and also to cache the resulting .o -target bpf
clang generated file so that we don't require that clang is present on
all machines where this will run.

Also needed is a way to do the copy in move_addr_to_kernel() _after_ the
size validation is done against sizeof(struct sockaddr_storage) (the
regs for the parms seems busted for that kprobes case, say,
move_addr_to_kernel:8) and convince the kernel bpf validator that this
will not copy more than whats gets validated by the kernel and no info
leak will take place (the 'record' variable is all zeroed by the
initialization of some of its fields (.ulen, for instance).

  # perf trace --ev bpf-output/no-inherit=1,name=__move_addr_to_kernel/ \
               --ev syscall_copy_pointer_args.c/map:__move_addr_to_kernel.event=__move_addr_to_kernel/ \
               ssh ipv6.google.com

  # cat syscall_copy_pointer_args.c
  #include <uapi/linux/socket.h>
  #include <uapi/linux/ptrace.h>
  #include <uapi/linux/bpf.h>
  #define SEC(NAME) __attribute__((section(NAME), used))

  /* x86_64 */
  #define PT_REGS_PARM1(x) ((x)->di)
  #define PT_REGS_PARM2(x) ((x)->si)
  #define PT_REGS_PARM3(x) ((x)->dx)

  #define AF_UNSPEC       0
  #define AF_UNIX         1       /* Unix domain sockets          */
  #define AF_LOCAL        1       /* POSIX name for AF_UNIX       */
  #define AF_INET         2       /* Internet IP Protocol         */
  #define AF_INET6	10	/* IP version 6			*/

  /* Internet address. */
  struct in_addr {
	  u32	s_addr;
  };

  #define __SOCK_SIZE__	16		/* sizeof(struct sockaddr)	*/
  struct sockaddr_in {
    __kernel_sa_family_t	sin_family;	/* Address family		*/
    __be16		sin_port;	/* Port number			*/
    struct in_addr	sin_addr;	/* Internet address		*/

    /* Pad to size of `struct sockaddr'. */
    unsigned char	__pad[__SOCK_SIZE__ - sizeof(short int) -
			sizeof(unsigned short int) - sizeof(struct in_addr)];
  };

  struct in6_addr {
	  union {
		  __u8		u6_addr8[16];
		  __be16		u6_addr16[8];
		  __be32		u6_addr32[4];
	  } in6_u;
  #define s6_addr			in6_u.u6_addr8
  #define s6_addr16		in6_u.u6_addr16
  #define s6_addr32		in6_u.u6_addr32
  };

  struct sockaddr_in6 {
	  unsigned short int	sin6_family;    /* AF_INET6 */
	  __be16			sin6_port;      /* Transport layer port # */
	  __be32			sin6_flowinfo;  /* IPv6 flow information */
	  struct in6_addr		sin6_addr;      /* IPv6 address */
	  __u32			sin6_scope_id;  /* scope id (new in RFC2553) */
  };

  #define UNIX_PATH_MAX	108

  struct sockaddr_un {
	  __kernel_sa_family_t sun_family; /* AF_UNIX */
	  char sun_path[UNIX_PATH_MAX];	/* pathname */
  };

  struct bpf_map_def {
	  unsigned int type, key_size, value_size, max_entries;
  };

  /*
   * Do not make this static, it has to be seen by the perf/bpf glue
   * code to create the bpf-output event automagically.
   */
  struct bpf_map_def SEC("maps") __move_addr_to_kernel = {
	  .type	     = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
	  .key_size    = sizeof(int),
	  .value_size  = sizeof(u32),
	  .max_entries = __NR_CPUS__,
  };

  static int (*get_smp_processor_id)(void) = (void *)BPF_FUNC_get_smp_processor_id;
  static int (*trace_printk)(const char *fmt, int fmt_size, ...) = (void *)BPF_FUNC_trace_printk;
  static int (*perf_event_output)(struct pt_regs *, struct bpf_map_def *, int, void *, unsigned long) = (void *)BPF_FUNC_perf_event_output;
  static int (*probe_read)(void *dst, int size, void *unsafe_ptr) = (void *)BPF_FUNC_probe_read;

  SEC("move_addr_to_kernel=move_addr_to_kernel")
  int move_addr_to_kernel(struct pt_regs *ctx, int err)
  {
	  char err_str[] = "move_addr_to_kernel %d\n";
          int ulen = PT_REGS_PARM2(ctx), rc;
	  struct __kernel_sockaddr_storage *uaddr = (void *)PT_REGS_PARM1(ctx);
	  struct {
		  void				 *uaddr;
		  int				 ulen;
		  struct __kernel_sockaddr_storage ss;
	  } record = {
		  .uaddr = (void *)PT_REGS_PARM1(ctx),
		  .ulen  = ulen,
	  };
	  int ss_len = offsetof(typeof(record), ss);

	  probe_read(&record.ss.ss_family, sizeof(record.ss.ss_family), uaddr);

	  switch (record.ss.ss_family) {
	  case AF_UNSPEC: return 0;
	  case AF_INET:  ss_len += sizeof(struct sockaddr_in);	break;
	  case AF_INET6: ss_len += sizeof(struct sockaddr_in6);	break;
	  case AF_LOCAL: ss_len += sizeof(struct sockaddr_un);	break;
	  default:       ss_len = sizeof(record.ss);		break;
	  }
	  probe_read(&record.ss,  ss_len, uaddr);
	  perf_event_output(ctx, &__move_addr_to_kernel, get_smp_processor_id(), &record, ss_len);
	  return 1;
  out_err:
	  trace_printk(err_str, sizeof(err_str), rc);
	  return 0;
  }
  char _license[] SEC("license") = "GPL";
  int _version SEC("version") = LINUX_VERSION_CODE;

Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
1 file changed