[iproute2] xfrm: add option to hide keys in state output

Message ID 20190107213133.87000-1-benedictwong@google.com
State Changes Requested
Delegated to: David Ahern
Headers show
Series
  • [iproute2] xfrm: add option to hide keys in state output
Related show

Commit Message

Benedict Wong Jan. 7, 2019, 9:31 p.m.
ip xfrm state show currently dumps keys unconditionally. This limits its
use in logging, as security information can be leaked.

This patch adds a nokeys option to ip xfrm ( state show | monitor ), which
prevents the printing of keys. This allows ip xfrm state show to be used
in logging without exposing keys.

Signed-off-by: Benedict Wong <benedictwong@google.com>
---
 ip/ipxfrm.c        | 45 +++++++++++++++++++++++++--------------------
 ip/xfrm.h          |  5 +++--
 ip/xfrm_monitor.c  |  7 +++++--
 ip/xfrm_state.c    | 27 ++++++++++++++++++++++-----
 man/man8/ip-xfrm.8 | 15 ++++++++++++++-
 5 files changed, 69 insertions(+), 30 deletions(-)

Comments

Florian Fainelli Jan. 7, 2019, 10:23 p.m. | #1
On 1/7/19 1:31 PM, Benedict Wong wrote:
> ip xfrm state show currently dumps keys unconditionally. This limits its
> use in logging, as security information can be leaked.
> 
> This patch adds a nokeys option to ip xfrm ( state show | monitor ), which
> prevents the printing of keys. This allows ip xfrm state show to be used
> in logging without exposing keys.

I have not yet looked at the kernel side, but assuming the keys are
stored on the kernel side as well (which is quite conceivably what is
happening for software IPsec), would not it be better to not request the
kernel not to dump the keys to begin with, that way a process ptracing
iproute2 or whatever, would also not have a chance to ex-filtrate those
keys? If this is not how this is implemented, apologies :)

> 
> Signed-off-by: Benedict Wong <benedictwong@google.com>
> ---
>  ip/ipxfrm.c        | 45 +++++++++++++++++++++++++--------------------
>  ip/xfrm.h          |  5 +++--
>  ip/xfrm_monitor.c  |  7 +++++--
>  ip/xfrm_state.c    | 27 ++++++++++++++++++++++-----
>  man/man8/ip-xfrm.8 | 15 ++++++++++++++-
>  5 files changed, 69 insertions(+), 30 deletions(-)
> 
> diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
> index 2dea4e37..1334ca9f 100644
> --- a/ip/ipxfrm.c
> +++ b/ip/ipxfrm.c
> @@ -497,7 +497,8 @@ void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
>  }
>  
>  static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> -			      FILE *fp, const char *prefix, int newline)
> +			      FILE *fp, const char *prefix, int newline,
> +			      bool nokeys)
>  {
>  	int keylen;
>  	int i;
> @@ -521,7 +522,9 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
>  		goto fin;
>  	}
>  
> -	if (keylen > 0) {
> +	if (nokeys)
> +		fprintf(fp, "<<Keys hidden>>");
> +	else if (keylen > 0) {
>  		fprintf(fp, "0x");
>  		for (i = 0; i < keylen; i++)
>  			fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
> @@ -536,13 +539,13 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
>  }
>  
>  static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> -				   FILE *fp, const char *prefix)
> +				   FILE *fp, const char *prefix, bool nokeys)
>  {
> -	return __xfrm_algo_print(algo, type, len, fp, prefix, 1);
> +	return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
>  }
>  
>  static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> -			    FILE *fp, const char *prefix)
> +			    FILE *fp, const char *prefix, bool nokeys)
>  {
>  	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
>  
> @@ -550,7 +553,8 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
>  	base_algo->alg_key_len = algo->alg_key_len;
>  	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
>  
> -	__xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
> +	__xfrm_algo_print(base_algo,
> +			  XFRMA_ALG_AEAD, len, fp, prefix, 0, nokeys);
>  
>  	fprintf(fp, " %d", algo->alg_icv_len);
>  
> @@ -558,7 +562,7 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
>  }
>  
>  static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
> -				  FILE *fp, const char *prefix)
> +				  FILE *fp, const char *prefix, bool nokeys)
>  {
>  	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
>  
> @@ -566,7 +570,8 @@ static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
>  	base_algo->alg_key_len = algo->alg_key_len;
>  	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
>  
> -	__xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
> +	__xfrm_algo_print(base_algo,
> +			  XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0, nokeys);
>  
>  	fprintf(fp, " %d", algo->alg_trunc_len);
>  
> @@ -679,7 +684,7 @@ done:
>  }
>  
>  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> -		      FILE *fp, const char *prefix)
> +		      FILE *fp, const char *prefix, bool nokeys)
>  {
>  	if (tb[XFRMA_MARK]) {
>  		struct rtattr *rta = tb[XFRMA_MARK];
> @@ -700,36 +705,36 @@ void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
>  	if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
>  		struct rtattr *rta = tb[XFRMA_ALG_AUTH];
>  
> -		xfrm_algo_print(RTA_DATA(rta),
> -				XFRMA_ALG_AUTH, RTA_PAYLOAD(rta), fp, prefix);
> +		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH,
> +				RTA_PAYLOAD(rta), fp, prefix, nokeys);
>  	}
>  
>  	if (tb[XFRMA_ALG_AUTH_TRUNC]) {
>  		struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
>  
>  		xfrm_auth_trunc_print(RTA_DATA(rta),
> -				      RTA_PAYLOAD(rta), fp, prefix);
> +				      RTA_PAYLOAD(rta), fp, prefix, nokeys);
>  	}
>  
>  	if (tb[XFRMA_ALG_AEAD]) {
>  		struct rtattr *rta = tb[XFRMA_ALG_AEAD];
>  
>  		xfrm_aead_print(RTA_DATA(rta),
> -				RTA_PAYLOAD(rta), fp, prefix);
> +				RTA_PAYLOAD(rta), fp, prefix, nokeys);
>  	}
>  
>  	if (tb[XFRMA_ALG_CRYPT]) {
>  		struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
>  
> -		xfrm_algo_print(RTA_DATA(rta),
> -				XFRMA_ALG_CRYPT, RTA_PAYLOAD(rta), fp, prefix);
> +		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
> +				RTA_PAYLOAD(rta), fp, prefix, nokeys);
>  	}
>  
>  	if (tb[XFRMA_ALG_COMP]) {
>  		struct rtattr *rta = tb[XFRMA_ALG_COMP];
>  
> -		xfrm_algo_print(RTA_DATA(rta),
> -				XFRMA_ALG_COMP, RTA_PAYLOAD(rta), fp, prefix);
> +		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP,
> +				RTA_PAYLOAD(rta), fp, prefix, nokeys);
>  	}
>  
>  	if (tb[XFRMA_ENCAP]) {
> @@ -897,7 +902,7 @@ static int xfrm_selector_iszero(struct xfrm_selector *s)
>  
>  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
>  			    struct rtattr *tb[], FILE *fp, const char *prefix,
> -			    const char *title)
> +			    const char *title, bool nokeys)
>  {
>  	char buf[STRBUF_SIZE] = {};
>  	int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
> @@ -943,7 +948,7 @@ void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
>  		fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
>  	fprintf(fp, "%s", _SL_);
>  
> -	xfrm_xfrma_print(tb, xsinfo->family, fp, buf);
> +	xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys);
>  
>  	if (!xfrm_selector_iszero(&xsinfo->sel)) {
>  		char sbuf[STRBUF_SIZE];
> @@ -1071,7 +1076,7 @@ void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
>  	if (show_stats > 0)
>  		xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
>  
> -	xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf);
> +	xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false);
>  }
>  
>  int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
> diff --git a/ip/xfrm.h b/ip/xfrm.h
> index 72390d79..9ba5ca61 100644
> --- a/ip/xfrm.h
> +++ b/ip/xfrm.h
> @@ -105,6 +105,7 @@ struct xfrm_filter {
>  extern struct xfrm_filter filter;
>  
>  int xfrm_state_print(struct nlmsghdr *n, void *arg);
> +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg);
>  int xfrm_policy_print(struct nlmsghdr *n, void *arg);
>  int do_xfrm_state(int argc, char **argv);
>  int do_xfrm_policy(int argc, char **argv);
> @@ -124,10 +125,10 @@ const char *strxf_ptype(__u8 ptype);
>  void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
>  			 FILE *fp, const char *prefix);
>  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> -		      FILE *fp, const char *prefix);
> +		      FILE *fp, const char *prefix, bool nokeys);
>  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
>  			    struct rtattr *tb[], FILE *fp, const char *prefix,
> -			   const char *title);
> +			   const char *title, bool nokeys);
>  void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
>  			    struct rtattr *tb[], FILE *fp, const char *prefix,
>  			    const char *title);
> diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
> index 76905ed3..17117f41 100644
> --- a/ip/xfrm_monitor.c
> +++ b/ip/xfrm_monitor.c
> @@ -35,10 +35,11 @@
>  
>  static void usage(void) __attribute__((noreturn));
>  static int listen_all_nsid;
> +static bool nokeys;
>  
>  static void usage(void)
>  {
> -	fprintf(stderr, "Usage: ip xfrm monitor [all-nsid] [ all | OBJECTS | help ]\n");
> +	fprintf(stderr, "Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n");
>  	fprintf(stderr, "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
>  	exit(-1);
>  }
> @@ -197,7 +198,7 @@ static int xfrm_report_print(struct nlmsghdr *n, void *arg)
>  
>  	parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
>  
> -	xfrm_xfrma_print(tb, family, fp, "  ");
> +	xfrm_xfrma_print(tb, family, fp, "  ", nokeys);
>  
>  	if (oneline)
>  		fprintf(fp, "\n");
> @@ -352,6 +353,8 @@ int do_xfrm_monitor(int argc, char **argv)
>  		if (matches(*argv, "file") == 0) {
>  			NEXT_ARG();
>  			file = *argv;
> +		} else if (strcmp(*argv, "nokeys") == 0) {
> +			nokeys = true;
>  		} else if (strcmp(*argv, "all") == 0) {
>  			/* fall out */
>  		} else if (matches(*argv, "all-nsid") == 0) {
> diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
> index e8c01746..d8f9350e 100644
> --- a/ip/xfrm_state.c
> +++ b/ip/xfrm_state.c
> @@ -65,7 +65,9 @@ static void usage(void)
>  	fprintf(stderr, "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n");
>  	fprintf(stderr, "        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n");
>  	fprintf(stderr, "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n");
> -	fprintf(stderr, "Usage: ip xfrm state { deleteall | list } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> +	fprintf(stderr, "Usage: ip xfrm state { deleteall } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> +	fprintf(stderr, "        [ flag FLAG-LIST ]\n");
> +	fprintf(stderr, "Usage: ip xfrm state { list } [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n");
>  	fprintf(stderr, "        [ flag FLAG-LIST ]\n");
>  	fprintf(stderr, "Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n");
>  	fprintf(stderr, "Usage: ip xfrm state count\n");
> @@ -908,7 +910,7 @@ static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
>  	return 1;
>  }
>  
> -int xfrm_state_print(struct nlmsghdr *n, void *arg)
> +static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
>  {
>  	FILE *fp = (FILE *)arg;
>  	struct rtattr *tb[XFRMA_MAX+1];
> @@ -979,7 +981,7 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
>  		xsinfo = RTA_DATA(tb[XFRMA_SA]);
>  	}
>  
> -	xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL);
> +	xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
>  
>  	if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
>  		fprintf(fp, "\t");
> @@ -994,6 +996,16 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
>  	return 0;
>  }
>  
> +int xfrm_state_print(struct nlmsghdr *n, void *arg)
> +{
> +	return __do_xfrm_state_print(n, arg, false);
> +}
> +
> +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
> +{
> +	return __do_xfrm_state_print(n, arg, true);
> +}
> +
>  static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
>  {
>  	struct rtnl_handle rth;
> @@ -1145,13 +1157,16 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
>  {
>  	char *idp = NULL;
>  	struct rtnl_handle rth;
> +	bool nokeys = false;
>  
>  	if (argc > 0)
>  		filter.use = 1;
>  	filter.xsinfo.family = preferred_family;
>  
>  	while (argc > 0) {
> -		if (strcmp(*argv, "mode") == 0) {
> +		if (strcmp(*argv, "nokeys") == 0) {
> +			nokeys = true;
> +		} else if (strcmp(*argv, "mode") == 0) {
>  			NEXT_ARG();
>  			xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
>  
> @@ -1267,7 +1282,9 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
>  			exit(1);
>  		}
>  
> -		if (rtnl_dump_filter(&rth, xfrm_state_print, stdout) < 0) {
> +		rtnl_filter_t filter = nokeys ?
> +				xfrm_state_print_nokeys : xfrm_state_print;
> +		if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
>  			fprintf(stderr, "Dump terminated\n");
>  			exit(1);
>  		}
> diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
> index 839e06aa..0fdf97d9 100644
> --- a/man/man8/ip-xfrm.8
> +++ b/man/man8/ip-xfrm.8
> @@ -89,7 +89,7 @@ ip-xfrm \- transform configuration
>  .IR MASK " ] ]"
>  
>  .ti -8
> -.BR "ip xfrm state" " { " deleteall " | " list " } ["
> +.BR "ip xfrm state" " { " deleteall " } ["
>  .IR ID " ]"
>  .RB "[ " mode
>  .IR MODE " ]"
> @@ -98,6 +98,17 @@ ip-xfrm \- transform configuration
>  .RB "[ " flag
>  .IR FLAG-LIST " ]"
>  
> +.ti -8
> +.BR "ip xfrm state" " { " list " } ["
> +.IR ID " ]"
> +.RB "[ " nokeys " ]"
> +.RB "[ " mode
> +.IR MODE " ]"
> +.RB "[ " reqid
> +.IR REQID " ]"
> +.RB "[ " flag
> +.IR FLAG-LIST " ]"
> +
>  .ti -8
>  .BR "ip xfrm state flush" " [ " proto
>  .IR XFRM-PROTO " ]"
> @@ -381,6 +392,8 @@ ip-xfrm \- transform configuration
>  .BR "ip xfrm monitor" " ["
>  .BI all-nsid
>  ] [
> +.BI nokeys
> +] [
>  .BI all
>   |
>  .IR LISTofXFRM-OBJECTS " ]"
>
Benedict Wong Jan. 7, 2019, 11:10 p.m. | #2
(Accidentally sent previously as direct reply. Re-sending as reply-all)

> ... would not it be better to not request the
> kernel not to dump the keys to begin with ...

I think it's still valid to have it in iproute2, since it does allow for
backward compatibility against older kernels. Adding it to the
kernels would mean it hits 5.1+ at the very most.

> ... that way a process ptracing
> iproute2 or whatever, would also not have a chance to ex-filtrate those
> keys? ...

That is a good point. I suspect the kernel will still need to have the
keys dumped default in order to maintain backwards compatibility, so
this does also add some safety. The primary goal of this patch was to
allow someone with (permanent) access to the to automatically
generate a debug log that doesn't compromise (eg a privileged system
daemon, or a superuser). Just my 2c though :)

On Mon, Jan 7, 2019 at 2:23 PM Florian Fainelli <f.fainelli@gmail.com> wrote:
>
> On 1/7/19 1:31 PM, Benedict Wong wrote:
> > ip xfrm state show currently dumps keys unconditionally. This limits its
> > use in logging, as security information can be leaked.
> >
> > This patch adds a nokeys option to ip xfrm ( state show | monitor ), which
> > prevents the printing of keys. This allows ip xfrm state show to be used
> > in logging without exposing keys.
>
> I have not yet looked at the kernel side, but assuming the keys are
> stored on the kernel side as well (which is quite conceivably what is
> happening for software IPsec), would not it be better to not request the
> kernel not to dump the keys to begin with, that way a process ptracing
> iproute2 or whatever, would also not have a chance to ex-filtrate those
> keys? If this is not how this is implemented, apologies :)
>
> >
> > Signed-off-by: Benedict Wong <benedictwong@google.com>
> > ---
> >  ip/ipxfrm.c        | 45 +++++++++++++++++++++++++--------------------
> >  ip/xfrm.h          |  5 +++--
> >  ip/xfrm_monitor.c  |  7 +++++--
> >  ip/xfrm_state.c    | 27 ++++++++++++++++++++++-----
> >  man/man8/ip-xfrm.8 | 15 ++++++++++++++-
> >  5 files changed, 69 insertions(+), 30 deletions(-)
> >
> > diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
> > index 2dea4e37..1334ca9f 100644
> > --- a/ip/ipxfrm.c
> > +++ b/ip/ipxfrm.c
> > @@ -497,7 +497,8 @@ void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
> >  }
> >
> >  static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > -                           FILE *fp, const char *prefix, int newline)
> > +                           FILE *fp, const char *prefix, int newline,
> > +                           bool nokeys)
> >  {
> >       int keylen;
> >       int i;
> > @@ -521,7 +522,9 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> >               goto fin;
> >       }
> >
> > -     if (keylen > 0) {
> > +     if (nokeys)
> > +             fprintf(fp, "<<Keys hidden>>");
> > +     else if (keylen > 0) {
> >               fprintf(fp, "0x");
> >               for (i = 0; i < keylen; i++)
> >                       fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
> > @@ -536,13 +539,13 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> >  }
> >
> >  static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > -                                FILE *fp, const char *prefix)
> > +                                FILE *fp, const char *prefix, bool nokeys)
> >  {
> > -     return __xfrm_algo_print(algo, type, len, fp, prefix, 1);
> > +     return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
> >  }
> >
> >  static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> > -                         FILE *fp, const char *prefix)
> > +                         FILE *fp, const char *prefix, bool nokeys)
> >  {
> >       struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
> >
> > @@ -550,7 +553,8 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> >       base_algo->alg_key_len = algo->alg_key_len;
> >       memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
> >
> > -     __xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
> > +     __xfrm_algo_print(base_algo,
> > +                       XFRMA_ALG_AEAD, len, fp, prefix, 0, nokeys);
> >
> >       fprintf(fp, " %d", algo->alg_icv_len);
> >
> > @@ -558,7 +562,7 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> >  }
> >
> >  static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
> > -                               FILE *fp, const char *prefix)
> > +                               FILE *fp, const char *prefix, bool nokeys)
> >  {
> >       struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
> >
> > @@ -566,7 +570,8 @@ static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
> >       base_algo->alg_key_len = algo->alg_key_len;
> >       memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
> >
> > -     __xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
> > +     __xfrm_algo_print(base_algo,
> > +                       XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0, nokeys);
> >
> >       fprintf(fp, " %d", algo->alg_trunc_len);
> >
> > @@ -679,7 +684,7 @@ done:
> >  }
> >
> >  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> > -                   FILE *fp, const char *prefix)
> > +                   FILE *fp, const char *prefix, bool nokeys)
> >  {
> >       if (tb[XFRMA_MARK]) {
> >               struct rtattr *rta = tb[XFRMA_MARK];
> > @@ -700,36 +705,36 @@ void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> >       if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
> >               struct rtattr *rta = tb[XFRMA_ALG_AUTH];
> >
> > -             xfrm_algo_print(RTA_DATA(rta),
> > -                             XFRMA_ALG_AUTH, RTA_PAYLOAD(rta), fp, prefix);
> > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH,
> > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> >       }
> >
> >       if (tb[XFRMA_ALG_AUTH_TRUNC]) {
> >               struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
> >
> >               xfrm_auth_trunc_print(RTA_DATA(rta),
> > -                                   RTA_PAYLOAD(rta), fp, prefix);
> > +                                   RTA_PAYLOAD(rta), fp, prefix, nokeys);
> >       }
> >
> >       if (tb[XFRMA_ALG_AEAD]) {
> >               struct rtattr *rta = tb[XFRMA_ALG_AEAD];
> >
> >               xfrm_aead_print(RTA_DATA(rta),
> > -                             RTA_PAYLOAD(rta), fp, prefix);
> > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> >       }
> >
> >       if (tb[XFRMA_ALG_CRYPT]) {
> >               struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
> >
> > -             xfrm_algo_print(RTA_DATA(rta),
> > -                             XFRMA_ALG_CRYPT, RTA_PAYLOAD(rta), fp, prefix);
> > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
> > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> >       }
> >
> >       if (tb[XFRMA_ALG_COMP]) {
> >               struct rtattr *rta = tb[XFRMA_ALG_COMP];
> >
> > -             xfrm_algo_print(RTA_DATA(rta),
> > -                             XFRMA_ALG_COMP, RTA_PAYLOAD(rta), fp, prefix);
> > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP,
> > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> >       }
> >
> >       if (tb[XFRMA_ENCAP]) {
> > @@ -897,7 +902,7 @@ static int xfrm_selector_iszero(struct xfrm_selector *s)
> >
> >  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> > -                         const char *title)
> > +                         const char *title, bool nokeys)
> >  {
> >       char buf[STRBUF_SIZE] = {};
> >       int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
> > @@ -943,7 +948,7 @@ void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> >               fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
> >       fprintf(fp, "%s", _SL_);
> >
> > -     xfrm_xfrma_print(tb, xsinfo->family, fp, buf);
> > +     xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys);
> >
> >       if (!xfrm_selector_iszero(&xsinfo->sel)) {
> >               char sbuf[STRBUF_SIZE];
> > @@ -1071,7 +1076,7 @@ void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
> >       if (show_stats > 0)
> >               xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
> >
> > -     xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf);
> > +     xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false);
> >  }
> >
> >  int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
> > diff --git a/ip/xfrm.h b/ip/xfrm.h
> > index 72390d79..9ba5ca61 100644
> > --- a/ip/xfrm.h
> > +++ b/ip/xfrm.h
> > @@ -105,6 +105,7 @@ struct xfrm_filter {
> >  extern struct xfrm_filter filter;
> >
> >  int xfrm_state_print(struct nlmsghdr *n, void *arg);
> > +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg);
> >  int xfrm_policy_print(struct nlmsghdr *n, void *arg);
> >  int do_xfrm_state(int argc, char **argv);
> >  int do_xfrm_policy(int argc, char **argv);
> > @@ -124,10 +125,10 @@ const char *strxf_ptype(__u8 ptype);
> >  void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
> >                        FILE *fp, const char *prefix);
> >  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> > -                   FILE *fp, const char *prefix);
> > +                   FILE *fp, const char *prefix, bool nokeys);
> >  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> > -                        const char *title);
> > +                        const char *title, bool nokeys);
> >  void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
> >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> >                           const char *title);
> > diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
> > index 76905ed3..17117f41 100644
> > --- a/ip/xfrm_monitor.c
> > +++ b/ip/xfrm_monitor.c
> > @@ -35,10 +35,11 @@
> >
> >  static void usage(void) __attribute__((noreturn));
> >  static int listen_all_nsid;
> > +static bool nokeys;
> >
> >  static void usage(void)
> >  {
> > -     fprintf(stderr, "Usage: ip xfrm monitor [all-nsid] [ all | OBJECTS | help ]\n");
> > +     fprintf(stderr, "Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n");
> >       fprintf(stderr, "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
> >       exit(-1);
> >  }
> > @@ -197,7 +198,7 @@ static int xfrm_report_print(struct nlmsghdr *n, void *arg)
> >
> >       parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
> >
> > -     xfrm_xfrma_print(tb, family, fp, "  ");
> > +     xfrm_xfrma_print(tb, family, fp, "  ", nokeys);
> >
> >       if (oneline)
> >               fprintf(fp, "\n");
> > @@ -352,6 +353,8 @@ int do_xfrm_monitor(int argc, char **argv)
> >               if (matches(*argv, "file") == 0) {
> >                       NEXT_ARG();
> >                       file = *argv;
> > +             } else if (strcmp(*argv, "nokeys") == 0) {
> > +                     nokeys = true;
> >               } else if (strcmp(*argv, "all") == 0) {
> >                       /* fall out */
> >               } else if (matches(*argv, "all-nsid") == 0) {
> > diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
> > index e8c01746..d8f9350e 100644
> > --- a/ip/xfrm_state.c
> > +++ b/ip/xfrm_state.c
> > @@ -65,7 +65,9 @@ static void usage(void)
> >       fprintf(stderr, "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n");
> >       fprintf(stderr, "        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n");
> >       fprintf(stderr, "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n");
> > -     fprintf(stderr, "Usage: ip xfrm state { deleteall | list } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> > +     fprintf(stderr, "Usage: ip xfrm state { deleteall } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> > +     fprintf(stderr, "        [ flag FLAG-LIST ]\n");
> > +     fprintf(stderr, "Usage: ip xfrm state { list } [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> >       fprintf(stderr, "        [ flag FLAG-LIST ]\n");
> >       fprintf(stderr, "Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n");
> >       fprintf(stderr, "Usage: ip xfrm state count\n");
> > @@ -908,7 +910,7 @@ static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
> >       return 1;
> >  }
> >
> > -int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > +static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
> >  {
> >       FILE *fp = (FILE *)arg;
> >       struct rtattr *tb[XFRMA_MAX+1];
> > @@ -979,7 +981,7 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
> >               xsinfo = RTA_DATA(tb[XFRMA_SA]);
> >       }
> >
> > -     xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL);
> > +     xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
> >
> >       if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
> >               fprintf(fp, "\t");
> > @@ -994,6 +996,16 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
> >       return 0;
> >  }
> >
> > +int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > +{
> > +     return __do_xfrm_state_print(n, arg, false);
> > +}
> > +
> > +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
> > +{
> > +     return __do_xfrm_state_print(n, arg, true);
> > +}
> > +
> >  static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
> >  {
> >       struct rtnl_handle rth;
> > @@ -1145,13 +1157,16 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
> >  {
> >       char *idp = NULL;
> >       struct rtnl_handle rth;
> > +     bool nokeys = false;
> >
> >       if (argc > 0)
> >               filter.use = 1;
> >       filter.xsinfo.family = preferred_family;
> >
> >       while (argc > 0) {
> > -             if (strcmp(*argv, "mode") == 0) {
> > +             if (strcmp(*argv, "nokeys") == 0) {
> > +                     nokeys = true;
> > +             } else if (strcmp(*argv, "mode") == 0) {
> >                       NEXT_ARG();
> >                       xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
> >
> > @@ -1267,7 +1282,9 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
> >                       exit(1);
> >               }
> >
> > -             if (rtnl_dump_filter(&rth, xfrm_state_print, stdout) < 0) {
> > +             rtnl_filter_t filter = nokeys ?
> > +                             xfrm_state_print_nokeys : xfrm_state_print;
> > +             if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
> >                       fprintf(stderr, "Dump terminated\n");
> >                       exit(1);
> >               }
> > diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
> > index 839e06aa..0fdf97d9 100644
> > --- a/man/man8/ip-xfrm.8
> > +++ b/man/man8/ip-xfrm.8
> > @@ -89,7 +89,7 @@ ip-xfrm \- transform configuration
> >  .IR MASK " ] ]"
> >
> >  .ti -8
> > -.BR "ip xfrm state" " { " deleteall " | " list " } ["
> > +.BR "ip xfrm state" " { " deleteall " } ["
> >  .IR ID " ]"
> >  .RB "[ " mode
> >  .IR MODE " ]"
> > @@ -98,6 +98,17 @@ ip-xfrm \- transform configuration
> >  .RB "[ " flag
> >  .IR FLAG-LIST " ]"
> >
> > +.ti -8
> > +.BR "ip xfrm state" " { " list " } ["
> > +.IR ID " ]"
> > +.RB "[ " nokeys " ]"
> > +.RB "[ " mode
> > +.IR MODE " ]"
> > +.RB "[ " reqid
> > +.IR REQID " ]"
> > +.RB "[ " flag
> > +.IR FLAG-LIST " ]"
> > +
> >  .ti -8
> >  .BR "ip xfrm state flush" " [ " proto
> >  .IR XFRM-PROTO " ]"
> > @@ -381,6 +392,8 @@ ip-xfrm \- transform configuration
> >  .BR "ip xfrm monitor" " ["
> >  .BI all-nsid
> >  ] [
> > +.BI nokeys
> > +] [
> >  .BI all
> >   |
> >  .IR LISTofXFRM-OBJECTS " ]"
> >
>
>
> --
> Florian
Benedict Wong Jan. 16, 2019, 2:52 a.m. | #3
Friendly ping for review. If there are no concerns, I think this would
be useful especially in the logging/bugreport use cases.

On Mon, Jan 7, 2019 at 3:10 PM Benedict Wong <benedictwong@google.com> wrote:
>
> (Accidentally sent previously as direct reply. Re-sending as reply-all)
>
> > ... would not it be better to not request the
> > kernel not to dump the keys to begin with ...
>
> I think it's still valid to have it in iproute2, since it does allow for
> backward compatibility against older kernels. Adding it to the
> kernels would mean it hits 5.1+ at the very most.
>
> > ... that way a process ptracing
> > iproute2 or whatever, would also not have a chance to ex-filtrate those
> > keys? ...
>
> That is a good point. I suspect the kernel will still need to have the
> keys dumped default in order to maintain backwards compatibility, so
> this does also add some safety. The primary goal of this patch was to
> allow someone with (permanent) access to the to automatically
> generate a debug log that doesn't compromise (eg a privileged system
> daemon, or a superuser). Just my 2c though :)
>
> On Mon, Jan 7, 2019 at 2:23 PM Florian Fainelli <f.fainelli@gmail.com> wrote:
> >
> > On 1/7/19 1:31 PM, Benedict Wong wrote:
> > > ip xfrm state show currently dumps keys unconditionally. This limits its
> > > use in logging, as security information can be leaked.
> > >
> > > This patch adds a nokeys option to ip xfrm ( state show | monitor ), which
> > > prevents the printing of keys. This allows ip xfrm state show to be used
> > > in logging without exposing keys.
> >
> > I have not yet looked at the kernel side, but assuming the keys are
> > stored on the kernel side as well (which is quite conceivably what is
> > happening for software IPsec), would not it be better to not request the
> > kernel not to dump the keys to begin with, that way a process ptracing
> > iproute2 or whatever, would also not have a chance to ex-filtrate those
> > keys? If this is not how this is implemented, apologies :)
> >
> > >
> > > Signed-off-by: Benedict Wong <benedictwong@google.com>
> > > ---
> > >  ip/ipxfrm.c        | 45 +++++++++++++++++++++++++--------------------
> > >  ip/xfrm.h          |  5 +++--
> > >  ip/xfrm_monitor.c  |  7 +++++--
> > >  ip/xfrm_state.c    | 27 ++++++++++++++++++++++-----
> > >  man/man8/ip-xfrm.8 | 15 ++++++++++++++-
> > >  5 files changed, 69 insertions(+), 30 deletions(-)
> > >
> > > diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
> > > index 2dea4e37..1334ca9f 100644
> > > --- a/ip/ipxfrm.c
> > > +++ b/ip/ipxfrm.c
> > > @@ -497,7 +497,8 @@ void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
> > >  }
> > >
> > >  static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > > -                           FILE *fp, const char *prefix, int newline)
> > > +                           FILE *fp, const char *prefix, int newline,
> > > +                           bool nokeys)
> > >  {
> > >       int keylen;
> > >       int i;
> > > @@ -521,7 +522,9 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > >               goto fin;
> > >       }
> > >
> > > -     if (keylen > 0) {
> > > +     if (nokeys)
> > > +             fprintf(fp, "<<Keys hidden>>");
> > > +     else if (keylen > 0) {
> > >               fprintf(fp, "0x");
> > >               for (i = 0; i < keylen; i++)
> > >                       fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
> > > @@ -536,13 +539,13 @@ static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > >  }
> > >
> > >  static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
> > > -                                FILE *fp, const char *prefix)
> > > +                                FILE *fp, const char *prefix, bool nokeys)
> > >  {
> > > -     return __xfrm_algo_print(algo, type, len, fp, prefix, 1);
> > > +     return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
> > >  }
> > >
> > >  static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> > > -                         FILE *fp, const char *prefix)
> > > +                         FILE *fp, const char *prefix, bool nokeys)
> > >  {
> > >       struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
> > >
> > > @@ -550,7 +553,8 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> > >       base_algo->alg_key_len = algo->alg_key_len;
> > >       memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
> > >
> > > -     __xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
> > > +     __xfrm_algo_print(base_algo,
> > > +                       XFRMA_ALG_AEAD, len, fp, prefix, 0, nokeys);
> > >
> > >       fprintf(fp, " %d", algo->alg_icv_len);
> > >
> > > @@ -558,7 +562,7 @@ static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
> > >  }
> > >
> > >  static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
> > > -                               FILE *fp, const char *prefix)
> > > +                               FILE *fp, const char *prefix, bool nokeys)
> > >  {
> > >       struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
> > >
> > > @@ -566,7 +570,8 @@ static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
> > >       base_algo->alg_key_len = algo->alg_key_len;
> > >       memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
> > >
> > > -     __xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
> > > +     __xfrm_algo_print(base_algo,
> > > +                       XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0, nokeys);
> > >
> > >       fprintf(fp, " %d", algo->alg_trunc_len);
> > >
> > > @@ -679,7 +684,7 @@ done:
> > >  }
> > >
> > >  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> > > -                   FILE *fp, const char *prefix)
> > > +                   FILE *fp, const char *prefix, bool nokeys)
> > >  {
> > >       if (tb[XFRMA_MARK]) {
> > >               struct rtattr *rta = tb[XFRMA_MARK];
> > > @@ -700,36 +705,36 @@ void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> > >       if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
> > >               struct rtattr *rta = tb[XFRMA_ALG_AUTH];
> > >
> > > -             xfrm_algo_print(RTA_DATA(rta),
> > > -                             XFRMA_ALG_AUTH, RTA_PAYLOAD(rta), fp, prefix);
> > > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH,
> > > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> > >       }
> > >
> > >       if (tb[XFRMA_ALG_AUTH_TRUNC]) {
> > >               struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
> > >
> > >               xfrm_auth_trunc_print(RTA_DATA(rta),
> > > -                                   RTA_PAYLOAD(rta), fp, prefix);
> > > +                                   RTA_PAYLOAD(rta), fp, prefix, nokeys);
> > >       }
> > >
> > >       if (tb[XFRMA_ALG_AEAD]) {
> > >               struct rtattr *rta = tb[XFRMA_ALG_AEAD];
> > >
> > >               xfrm_aead_print(RTA_DATA(rta),
> > > -                             RTA_PAYLOAD(rta), fp, prefix);
> > > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> > >       }
> > >
> > >       if (tb[XFRMA_ALG_CRYPT]) {
> > >               struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
> > >
> > > -             xfrm_algo_print(RTA_DATA(rta),
> > > -                             XFRMA_ALG_CRYPT, RTA_PAYLOAD(rta), fp, prefix);
> > > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
> > > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> > >       }
> > >
> > >       if (tb[XFRMA_ALG_COMP]) {
> > >               struct rtattr *rta = tb[XFRMA_ALG_COMP];
> > >
> > > -             xfrm_algo_print(RTA_DATA(rta),
> > > -                             XFRMA_ALG_COMP, RTA_PAYLOAD(rta), fp, prefix);
> > > +             xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP,
> > > +                             RTA_PAYLOAD(rta), fp, prefix, nokeys);
> > >       }
> > >
> > >       if (tb[XFRMA_ENCAP]) {
> > > @@ -897,7 +902,7 @@ static int xfrm_selector_iszero(struct xfrm_selector *s)
> > >
> > >  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> > >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> > > -                         const char *title)
> > > +                         const char *title, bool nokeys)
> > >  {
> > >       char buf[STRBUF_SIZE] = {};
> > >       int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
> > > @@ -943,7 +948,7 @@ void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> > >               fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
> > >       fprintf(fp, "%s", _SL_);
> > >
> > > -     xfrm_xfrma_print(tb, xsinfo->family, fp, buf);
> > > +     xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys);
> > >
> > >       if (!xfrm_selector_iszero(&xsinfo->sel)) {
> > >               char sbuf[STRBUF_SIZE];
> > > @@ -1071,7 +1076,7 @@ void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
> > >       if (show_stats > 0)
> > >               xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
> > >
> > > -     xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf);
> > > +     xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false);
> > >  }
> > >
> > >  int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
> > > diff --git a/ip/xfrm.h b/ip/xfrm.h
> > > index 72390d79..9ba5ca61 100644
> > > --- a/ip/xfrm.h
> > > +++ b/ip/xfrm.h
> > > @@ -105,6 +105,7 @@ struct xfrm_filter {
> > >  extern struct xfrm_filter filter;
> > >
> > >  int xfrm_state_print(struct nlmsghdr *n, void *arg);
> > > +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg);
> > >  int xfrm_policy_print(struct nlmsghdr *n, void *arg);
> > >  int do_xfrm_state(int argc, char **argv);
> > >  int do_xfrm_policy(int argc, char **argv);
> > > @@ -124,10 +125,10 @@ const char *strxf_ptype(__u8 ptype);
> > >  void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
> > >                        FILE *fp, const char *prefix);
> > >  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
> > > -                   FILE *fp, const char *prefix);
> > > +                   FILE *fp, const char *prefix, bool nokeys);
> > >  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
> > >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> > > -                        const char *title);
> > > +                        const char *title, bool nokeys);
> > >  void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
> > >                           struct rtattr *tb[], FILE *fp, const char *prefix,
> > >                           const char *title);
> > > diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
> > > index 76905ed3..17117f41 100644
> > > --- a/ip/xfrm_monitor.c
> > > +++ b/ip/xfrm_monitor.c
> > > @@ -35,10 +35,11 @@
> > >
> > >  static void usage(void) __attribute__((noreturn));
> > >  static int listen_all_nsid;
> > > +static bool nokeys;
> > >
> > >  static void usage(void)
> > >  {
> > > -     fprintf(stderr, "Usage: ip xfrm monitor [all-nsid] [ all | OBJECTS | help ]\n");
> > > +     fprintf(stderr, "Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n");
> > >       fprintf(stderr, "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
> > >       exit(-1);
> > >  }
> > > @@ -197,7 +198,7 @@ static int xfrm_report_print(struct nlmsghdr *n, void *arg)
> > >
> > >       parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
> > >
> > > -     xfrm_xfrma_print(tb, family, fp, "  ");
> > > +     xfrm_xfrma_print(tb, family, fp, "  ", nokeys);
> > >
> > >       if (oneline)
> > >               fprintf(fp, "\n");
> > > @@ -352,6 +353,8 @@ int do_xfrm_monitor(int argc, char **argv)
> > >               if (matches(*argv, "file") == 0) {
> > >                       NEXT_ARG();
> > >                       file = *argv;
> > > +             } else if (strcmp(*argv, "nokeys") == 0) {
> > > +                     nokeys = true;
> > >               } else if (strcmp(*argv, "all") == 0) {
> > >                       /* fall out */
> > >               } else if (matches(*argv, "all-nsid") == 0) {
> > > diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
> > > index e8c01746..d8f9350e 100644
> > > --- a/ip/xfrm_state.c
> > > +++ b/ip/xfrm_state.c
> > > @@ -65,7 +65,9 @@ static void usage(void)
> > >       fprintf(stderr, "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n");
> > >       fprintf(stderr, "        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n");
> > >       fprintf(stderr, "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n");
> > > -     fprintf(stderr, "Usage: ip xfrm state { deleteall | list } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> > > +     fprintf(stderr, "Usage: ip xfrm state { deleteall } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> > > +     fprintf(stderr, "        [ flag FLAG-LIST ]\n");
> > > +     fprintf(stderr, "Usage: ip xfrm state { list } [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n");
> > >       fprintf(stderr, "        [ flag FLAG-LIST ]\n");
> > >       fprintf(stderr, "Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n");
> > >       fprintf(stderr, "Usage: ip xfrm state count\n");
> > > @@ -908,7 +910,7 @@ static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
> > >       return 1;
> > >  }
> > >
> > > -int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > > +static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
> > >  {
> > >       FILE *fp = (FILE *)arg;
> > >       struct rtattr *tb[XFRMA_MAX+1];
> > > @@ -979,7 +981,7 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > >               xsinfo = RTA_DATA(tb[XFRMA_SA]);
> > >       }
> > >
> > > -     xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL);
> > > +     xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
> > >
> > >       if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
> > >               fprintf(fp, "\t");
> > > @@ -994,6 +996,16 @@ int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > >       return 0;
> > >  }
> > >
> > > +int xfrm_state_print(struct nlmsghdr *n, void *arg)
> > > +{
> > > +     return __do_xfrm_state_print(n, arg, false);
> > > +}
> > > +
> > > +int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
> > > +{
> > > +     return __do_xfrm_state_print(n, arg, true);
> > > +}
> > > +
> > >  static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
> > >  {
> > >       struct rtnl_handle rth;
> > > @@ -1145,13 +1157,16 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
> > >  {
> > >       char *idp = NULL;
> > >       struct rtnl_handle rth;
> > > +     bool nokeys = false;
> > >
> > >       if (argc > 0)
> > >               filter.use = 1;
> > >       filter.xsinfo.family = preferred_family;
> > >
> > >       while (argc > 0) {
> > > -             if (strcmp(*argv, "mode") == 0) {
> > > +             if (strcmp(*argv, "nokeys") == 0) {
> > > +                     nokeys = true;
> > > +             } else if (strcmp(*argv, "mode") == 0) {
> > >                       NEXT_ARG();
> > >                       xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
> > >
> > > @@ -1267,7 +1282,9 @@ static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
> > >                       exit(1);
> > >               }
> > >
> > > -             if (rtnl_dump_filter(&rth, xfrm_state_print, stdout) < 0) {
> > > +             rtnl_filter_t filter = nokeys ?
> > > +                             xfrm_state_print_nokeys : xfrm_state_print;
> > > +             if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
> > >                       fprintf(stderr, "Dump terminated\n");
> > >                       exit(1);
> > >               }
> > > diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
> > > index 839e06aa..0fdf97d9 100644
> > > --- a/man/man8/ip-xfrm.8
> > > +++ b/man/man8/ip-xfrm.8
> > > @@ -89,7 +89,7 @@ ip-xfrm \- transform configuration
> > >  .IR MASK " ] ]"
> > >
> > >  .ti -8
> > > -.BR "ip xfrm state" " { " deleteall " | " list " } ["
> > > +.BR "ip xfrm state" " { " deleteall " } ["
> > >  .IR ID " ]"
> > >  .RB "[ " mode
> > >  .IR MODE " ]"
> > > @@ -98,6 +98,17 @@ ip-xfrm \- transform configuration
> > >  .RB "[ " flag
> > >  .IR FLAG-LIST " ]"
> > >
> > > +.ti -8
> > > +.BR "ip xfrm state" " { " list " } ["
> > > +.IR ID " ]"
> > > +.RB "[ " nokeys " ]"
> > > +.RB "[ " mode
> > > +.IR MODE " ]"
> > > +.RB "[ " reqid
> > > +.IR REQID " ]"
> > > +.RB "[ " flag
> > > +.IR FLAG-LIST " ]"
> > > +
> > >  .ti -8
> > >  .BR "ip xfrm state flush" " [ " proto
> > >  .IR XFRM-PROTO " ]"
> > > @@ -381,6 +392,8 @@ ip-xfrm \- transform configuration
> > >  .BR "ip xfrm monitor" " ["
> > >  .BI all-nsid
> > >  ] [
> > > +.BI nokeys
> > > +] [
> > >  .BI all
> > >   |
> > >  .IR LISTofXFRM-OBJECTS " ]"
> > >
> >
> >
> > --
> > Florian

Patch

diff --git a/ip/ipxfrm.c b/ip/ipxfrm.c
index 2dea4e37..1334ca9f 100644
--- a/ip/ipxfrm.c
+++ b/ip/ipxfrm.c
@@ -497,7 +497,8 @@  void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
 }
 
 static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
-			      FILE *fp, const char *prefix, int newline)
+			      FILE *fp, const char *prefix, int newline,
+			      bool nokeys)
 {
 	int keylen;
 	int i;
@@ -521,7 +522,9 @@  static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
 		goto fin;
 	}
 
-	if (keylen > 0) {
+	if (nokeys)
+		fprintf(fp, "<<Keys hidden>>");
+	else if (keylen > 0) {
 		fprintf(fp, "0x");
 		for (i = 0; i < keylen; i++)
 			fprintf(fp, "%.2x", (unsigned char)algo->alg_key[i]);
@@ -536,13 +539,13 @@  static void __xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
 }
 
 static inline void xfrm_algo_print(struct xfrm_algo *algo, int type, int len,
-				   FILE *fp, const char *prefix)
+				   FILE *fp, const char *prefix, bool nokeys)
 {
-	return __xfrm_algo_print(algo, type, len, fp, prefix, 1);
+	return __xfrm_algo_print(algo, type, len, fp, prefix, 1, nokeys);
 }
 
 static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
-			    FILE *fp, const char *prefix)
+			    FILE *fp, const char *prefix, bool nokeys)
 {
 	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
 
@@ -550,7 +553,8 @@  static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
 	base_algo->alg_key_len = algo->alg_key_len;
 	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
 
-	__xfrm_algo_print(base_algo, XFRMA_ALG_AEAD, len, fp, prefix, 0);
+	__xfrm_algo_print(base_algo,
+			  XFRMA_ALG_AEAD, len, fp, prefix, 0, nokeys);
 
 	fprintf(fp, " %d", algo->alg_icv_len);
 
@@ -558,7 +562,7 @@  static void xfrm_aead_print(struct xfrm_algo_aead *algo, int len,
 }
 
 static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
-				  FILE *fp, const char *prefix)
+				  FILE *fp, const char *prefix, bool nokeys)
 {
 	struct xfrm_algo *base_algo = alloca(sizeof(*base_algo) + algo->alg_key_len / 8);
 
@@ -566,7 +570,8 @@  static void xfrm_auth_trunc_print(struct xfrm_algo_auth *algo, int len,
 	base_algo->alg_key_len = algo->alg_key_len;
 	memcpy(base_algo->alg_key, algo->alg_key, algo->alg_key_len / 8);
 
-	__xfrm_algo_print(base_algo, XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0);
+	__xfrm_algo_print(base_algo,
+			  XFRMA_ALG_AUTH_TRUNC, len, fp, prefix, 0, nokeys);
 
 	fprintf(fp, " %d", algo->alg_trunc_len);
 
@@ -679,7 +684,7 @@  done:
 }
 
 void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
-		      FILE *fp, const char *prefix)
+		      FILE *fp, const char *prefix, bool nokeys)
 {
 	if (tb[XFRMA_MARK]) {
 		struct rtattr *rta = tb[XFRMA_MARK];
@@ -700,36 +705,36 @@  void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
 	if (tb[XFRMA_ALG_AUTH] && !tb[XFRMA_ALG_AUTH_TRUNC]) {
 		struct rtattr *rta = tb[XFRMA_ALG_AUTH];
 
-		xfrm_algo_print(RTA_DATA(rta),
-				XFRMA_ALG_AUTH, RTA_PAYLOAD(rta), fp, prefix);
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_AUTH,
+				RTA_PAYLOAD(rta), fp, prefix, nokeys);
 	}
 
 	if (tb[XFRMA_ALG_AUTH_TRUNC]) {
 		struct rtattr *rta = tb[XFRMA_ALG_AUTH_TRUNC];
 
 		xfrm_auth_trunc_print(RTA_DATA(rta),
-				      RTA_PAYLOAD(rta), fp, prefix);
+				      RTA_PAYLOAD(rta), fp, prefix, nokeys);
 	}
 
 	if (tb[XFRMA_ALG_AEAD]) {
 		struct rtattr *rta = tb[XFRMA_ALG_AEAD];
 
 		xfrm_aead_print(RTA_DATA(rta),
-				RTA_PAYLOAD(rta), fp, prefix);
+				RTA_PAYLOAD(rta), fp, prefix, nokeys);
 	}
 
 	if (tb[XFRMA_ALG_CRYPT]) {
 		struct rtattr *rta = tb[XFRMA_ALG_CRYPT];
 
-		xfrm_algo_print(RTA_DATA(rta),
-				XFRMA_ALG_CRYPT, RTA_PAYLOAD(rta), fp, prefix);
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_CRYPT,
+				RTA_PAYLOAD(rta), fp, prefix, nokeys);
 	}
 
 	if (tb[XFRMA_ALG_COMP]) {
 		struct rtattr *rta = tb[XFRMA_ALG_COMP];
 
-		xfrm_algo_print(RTA_DATA(rta),
-				XFRMA_ALG_COMP, RTA_PAYLOAD(rta), fp, prefix);
+		xfrm_algo_print(RTA_DATA(rta), XFRMA_ALG_COMP,
+				RTA_PAYLOAD(rta), fp, prefix, nokeys);
 	}
 
 	if (tb[XFRMA_ENCAP]) {
@@ -897,7 +902,7 @@  static int xfrm_selector_iszero(struct xfrm_selector *s)
 
 void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
 			    struct rtattr *tb[], FILE *fp, const char *prefix,
-			    const char *title)
+			    const char *title, bool nokeys)
 {
 	char buf[STRBUF_SIZE] = {};
 	int force_spi = xfrm_xfrmproto_is_ipsec(xsinfo->id.proto);
@@ -943,7 +948,7 @@  void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
 		fprintf(fp, " (0x%s)", strxf_mask8(xsinfo->flags));
 	fprintf(fp, "%s", _SL_);
 
-	xfrm_xfrma_print(tb, xsinfo->family, fp, buf);
+	xfrm_xfrma_print(tb, xsinfo->family, fp, buf, nokeys);
 
 	if (!xfrm_selector_iszero(&xsinfo->sel)) {
 		char sbuf[STRBUF_SIZE];
@@ -1071,7 +1076,7 @@  void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
 	if (show_stats > 0)
 		xfrm_lifetime_print(&xpinfo->lft, &xpinfo->curlft, fp, buf);
 
-	xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf);
+	xfrm_xfrma_print(tb, xpinfo->sel.family, fp, buf, false);
 }
 
 int xfrm_id_parse(xfrm_address_t *saddr, struct xfrm_id *id, __u16 *family,
diff --git a/ip/xfrm.h b/ip/xfrm.h
index 72390d79..9ba5ca61 100644
--- a/ip/xfrm.h
+++ b/ip/xfrm.h
@@ -105,6 +105,7 @@  struct xfrm_filter {
 extern struct xfrm_filter filter;
 
 int xfrm_state_print(struct nlmsghdr *n, void *arg);
+int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg);
 int xfrm_policy_print(struct nlmsghdr *n, void *arg);
 int do_xfrm_state(int argc, char **argv);
 int do_xfrm_policy(int argc, char **argv);
@@ -124,10 +125,10 @@  const char *strxf_ptype(__u8 ptype);
 void xfrm_selector_print(struct xfrm_selector *sel, __u16 family,
 			 FILE *fp, const char *prefix);
 void xfrm_xfrma_print(struct rtattr *tb[], __u16 family,
-		      FILE *fp, const char *prefix);
+		      FILE *fp, const char *prefix, bool nokeys);
 void xfrm_state_info_print(struct xfrm_usersa_info *xsinfo,
 			    struct rtattr *tb[], FILE *fp, const char *prefix,
-			   const char *title);
+			   const char *title, bool nokeys);
 void xfrm_policy_info_print(struct xfrm_userpolicy_info *xpinfo,
 			    struct rtattr *tb[], FILE *fp, const char *prefix,
 			    const char *title);
diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c
index 76905ed3..17117f41 100644
--- a/ip/xfrm_monitor.c
+++ b/ip/xfrm_monitor.c
@@ -35,10 +35,11 @@ 
 
 static void usage(void) __attribute__((noreturn));
 static int listen_all_nsid;
+static bool nokeys;
 
 static void usage(void)
 {
-	fprintf(stderr, "Usage: ip xfrm monitor [all-nsid] [ all | OBJECTS | help ]\n");
+	fprintf(stderr, "Usage: ip xfrm monitor [ nokeys ] [ all-nsid ] [ all | OBJECTS | help ]\n");
 	fprintf(stderr, "OBJECTS := { acquire | expire | SA | aevent | policy | report }\n");
 	exit(-1);
 }
@@ -197,7 +198,7 @@  static int xfrm_report_print(struct nlmsghdr *n, void *arg)
 
 	parse_rtattr(tb, XFRMA_MAX, XFRMREP_RTA(xrep), len);
 
-	xfrm_xfrma_print(tb, family, fp, "  ");
+	xfrm_xfrma_print(tb, family, fp, "  ", nokeys);
 
 	if (oneline)
 		fprintf(fp, "\n");
@@ -352,6 +353,8 @@  int do_xfrm_monitor(int argc, char **argv)
 		if (matches(*argv, "file") == 0) {
 			NEXT_ARG();
 			file = *argv;
+		} else if (strcmp(*argv, "nokeys") == 0) {
+			nokeys = true;
 		} else if (strcmp(*argv, "all") == 0) {
 			/* fall out */
 		} else if (matches(*argv, "all-nsid") == 0) {
diff --git a/ip/xfrm_state.c b/ip/xfrm_state.c
index e8c01746..d8f9350e 100644
--- a/ip/xfrm_state.c
+++ b/ip/xfrm_state.c
@@ -65,7 +65,9 @@  static void usage(void)
 	fprintf(stderr, "Usage: ip xfrm state allocspi ID [ mode MODE ] [ mark MARK [ mask MASK ] ]\n");
 	fprintf(stderr, "        [ reqid REQID ] [ seq SEQ ] [ min SPI max SPI ]\n");
 	fprintf(stderr, "Usage: ip xfrm state { delete | get } ID [ mark MARK [ mask MASK ] ]\n");
-	fprintf(stderr, "Usage: ip xfrm state { deleteall | list } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
+	fprintf(stderr, "Usage: ip xfrm state { deleteall } [ ID ] [ mode MODE ] [ reqid REQID ]\n");
+	fprintf(stderr, "        [ flag FLAG-LIST ]\n");
+	fprintf(stderr, "Usage: ip xfrm state { list } [ nokeys ] [ ID ] [ mode MODE ] [ reqid REQID ]\n");
 	fprintf(stderr, "        [ flag FLAG-LIST ]\n");
 	fprintf(stderr, "Usage: ip xfrm state flush [ proto XFRM-PROTO ]\n");
 	fprintf(stderr, "Usage: ip xfrm state count\n");
@@ -908,7 +910,7 @@  static int xfrm_state_filter_match(struct xfrm_usersa_info *xsinfo)
 	return 1;
 }
 
-int xfrm_state_print(struct nlmsghdr *n, void *arg)
+static int __do_xfrm_state_print(struct nlmsghdr *n, void *arg, bool nokeys)
 {
 	FILE *fp = (FILE *)arg;
 	struct rtattr *tb[XFRMA_MAX+1];
@@ -979,7 +981,7 @@  int xfrm_state_print(struct nlmsghdr *n, void *arg)
 		xsinfo = RTA_DATA(tb[XFRMA_SA]);
 	}
 
-	xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL);
+	xfrm_state_info_print(xsinfo, tb, fp, NULL, NULL, nokeys);
 
 	if (n->nlmsg_type == XFRM_MSG_EXPIRE) {
 		fprintf(fp, "\t");
@@ -994,6 +996,16 @@  int xfrm_state_print(struct nlmsghdr *n, void *arg)
 	return 0;
 }
 
+int xfrm_state_print(struct nlmsghdr *n, void *arg)
+{
+	return __do_xfrm_state_print(n, arg, false);
+}
+
+int xfrm_state_print_nokeys(struct nlmsghdr *n, void *arg)
+{
+	return __do_xfrm_state_print(n, arg, true);
+}
+
 static int xfrm_state_get_or_delete(int argc, char **argv, int delete)
 {
 	struct rtnl_handle rth;
@@ -1145,13 +1157,16 @@  static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
 {
 	char *idp = NULL;
 	struct rtnl_handle rth;
+	bool nokeys = false;
 
 	if (argc > 0)
 		filter.use = 1;
 	filter.xsinfo.family = preferred_family;
 
 	while (argc > 0) {
-		if (strcmp(*argv, "mode") == 0) {
+		if (strcmp(*argv, "nokeys") == 0) {
+			nokeys = true;
+		} else if (strcmp(*argv, "mode") == 0) {
 			NEXT_ARG();
 			xfrm_mode_parse(&filter.xsinfo.mode, &argc, &argv);
 
@@ -1267,7 +1282,9 @@  static int xfrm_state_list_or_deleteall(int argc, char **argv, int deleteall)
 			exit(1);
 		}
 
-		if (rtnl_dump_filter(&rth, xfrm_state_print, stdout) < 0) {
+		rtnl_filter_t filter = nokeys ?
+				xfrm_state_print_nokeys : xfrm_state_print;
+		if (rtnl_dump_filter(&rth, filter, stdout) < 0) {
 			fprintf(stderr, "Dump terminated\n");
 			exit(1);
 		}
diff --git a/man/man8/ip-xfrm.8 b/man/man8/ip-xfrm.8
index 839e06aa..0fdf97d9 100644
--- a/man/man8/ip-xfrm.8
+++ b/man/man8/ip-xfrm.8
@@ -89,7 +89,7 @@  ip-xfrm \- transform configuration
 .IR MASK " ] ]"
 
 .ti -8
-.BR "ip xfrm state" " { " deleteall " | " list " } ["
+.BR "ip xfrm state" " { " deleteall " } ["
 .IR ID " ]"
 .RB "[ " mode
 .IR MODE " ]"
@@ -98,6 +98,17 @@  ip-xfrm \- transform configuration
 .RB "[ " flag
 .IR FLAG-LIST " ]"
 
+.ti -8
+.BR "ip xfrm state" " { " list " } ["
+.IR ID " ]"
+.RB "[ " nokeys " ]"
+.RB "[ " mode
+.IR MODE " ]"
+.RB "[ " reqid
+.IR REQID " ]"
+.RB "[ " flag
+.IR FLAG-LIST " ]"
+
 .ti -8
 .BR "ip xfrm state flush" " [ " proto
 .IR XFRM-PROTO " ]"
@@ -381,6 +392,8 @@  ip-xfrm \- transform configuration
 .BR "ip xfrm monitor" " ["
 .BI all-nsid
 ] [
+.BI nokeys
+] [
 .BI all
  |
 .IR LISTofXFRM-OBJECTS " ]"