diff mbox

[next] iptables: on revision mismatch, do not call print/save

Message ID 1481235401-46043-1-git-send-email-willemdebruijn.kernel@gmail.com
State Accepted
Delegated to: Pablo Neira
Headers show

Commit Message

Willem de Bruijn Dec. 8, 2016, 10:16 p.m. UTC
From: Willem de Bruijn <willemb@google.com>

Between revisions, the layout of xtables data may change completely.
Do not interpret the data in a revision M with a module of revision N.

Signed-off-by: Willem de Bruijn <willemb@google.com>
---
 iptables/ip6tables.c | 18 ++++++++++++++----
 iptables/iptables.c  | 18 ++++++++++++++----
 2 files changed, 28 insertions(+), 8 deletions(-)

Comments

Pablo Neira Ayuso Dec. 11, 2016, 7:55 p.m. UTC | #1
On Thu, Dec 08, 2016 at 05:16:41PM -0500, Willem de Bruijn wrote:
> From: Willem de Bruijn <willemb@google.com>
> 
> Between revisions, the layout of xtables data may change completely.
> Do not interpret the data in a revision M with a module of revision N.

Applied, thanks Willem.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jozsef Kadlecsik April 21, 2017, 8:15 p.m. UTC | #2
Hi,

On Thu, 8 Dec 2016, Willem de Bruijn wrote:

> From: Willem de Bruijn <willemb@google.com>
> 
> Between revisions, the layout of xtables data may change completely.
> Do not interpret the data in a revision M with a module of revision N.
> 
> Signed-off-by: Willem de Bruijn <willemb@google.com>
> ---
>  iptables/ip6tables.c | 18 ++++++++++++++----
>  iptables/iptables.c  | 18 ++++++++++++++----
>  2 files changed, 28 insertions(+), 8 deletions(-)

The patch breaks backward/forward compatibility in a match/target.

When the list of the revisions of a given match/target of iptables is not 
exactly the same as for the kernel counter part (when the kernel module 
supports less revisions than iptables), then in spite of the supported 
match/target, " [unsupported revision]" is printed instead of the 
arguments. See https://bugzilla.netfilter.org/show_bug.cgi?id=1147.

Please consider reverting the patch. Or we should not stop in 
xtables_find_match/xtables_find_target at revision checking when the 
revision does not match, until all possibilities is not exhausted.

Best regards,
Jozsef

> diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
> index c8d34e2..0d09181 100644
> --- a/iptables/ip6tables.c
> +++ b/iptables/ip6tables.c
> @@ -76,6 +76,8 @@ static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
>  static const char optflags[]
>  = { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c'};
>  
> +static const char unsupported_rev[] = " [unsupported revision]";
> +
>  static struct option original_opts[] = {
>  	{.name = "append",        .has_arg = 1, .val = 'A'},
>  	{.name = "delete",        .has_arg = 1, .val = 'D'},
> @@ -487,8 +489,10 @@ print_match(const struct xt_entry_match *m,
>  		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
>  
>  	if (match) {
> -		if (match->print)
> +		if (match->print && m->u.user.revision == match->revision)
>  			match->print(ip, m, numeric);
> +		else if (match->print)
> +			printf("%s%s ", match->name, unsupported_rev);
>  		else
>  			printf("%s ", match->name);
>  	} else {
> @@ -614,9 +618,11 @@ print_firewall(const struct ip6t_entry *fw,
>  	IP6T_MATCH_ITERATE(fw, print_match, &fw->ipv6, format & FMT_NUMERIC);
>  
>  	if (target) {
> -		if (target->print)
> +		if (target->print && t->u.user.revision == target->revision)
>  			/* Print the target information. */
>  			target->print(&fw->ipv6, t, format & FMT_NUMERIC);
> +		else if (target->print)
> +			printf(" %s%s", target->name, unsupported_rev);
>  	} else if (t->u.target_size != sizeof(*t))
>  		printf("[%u bytes of unknown target data] ",
>  		       (unsigned int)(t->u.target_size - sizeof(*t)));
> @@ -1004,8 +1010,10 @@ static int print_match_save(const struct xt_entry_match *e,
>  			match->alias ? match->alias(e) : e->u.user.name);
>  
>  		/* some matches don't provide a save function */
> -		if (match->save)
> +		if (match->save && e->u.user.revision == match->revision)
>  			match->save(ip, e);
> +		else if (match->save)
> +			printf(unsupported_rev);
>  	} else {
>  		if (e->u.match_size) {
>  			fprintf(stderr,
> @@ -1104,8 +1112,10 @@ void print_rule6(const struct ip6t_entry *e,
>  		}
>  
>  		printf(" -j %s", target->alias ? target->alias(t) : target_name);
> -		if (target->save)
> +		if (target->save && t->u.user.revision == target->revision)
>  			target->save(&e->ipv6, t);
> +		else if (target->save)
> +			printf(unsupported_rev);
>  		else {
>  			/* If the target size is greater than xt_entry_target
>  			 * there is something to be saved, we just don't know
> diff --git a/iptables/iptables.c b/iptables/iptables.c
> index 79fa37b..1bdde27 100644
> --- a/iptables/iptables.c
> +++ b/iptables/iptables.c
> @@ -73,6 +73,8 @@ static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
>  static const char optflags[]
>  = { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'};
>  
> +static const char unsupported_rev[] = " [unsupported revision]";
> +
>  static struct option original_opts[] = {
>  	{.name = "append",        .has_arg = 1, .val = 'A'},
>  	{.name = "delete",        .has_arg = 1, .val = 'D'},
> @@ -472,8 +474,10 @@ print_match(const struct xt_entry_match *m,
>  		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
>  
>  	if (match) {
> -		if (match->print)
> +		if (match->print && m->u.user.revision == match->revision)
>  			match->print(ip, m, numeric);
> +		else if (match->print)
> +			printf("%s%s ", match->name, unsupported_rev);
>  		else
>  			printf("%s ", match->name);
>  	} else {
> @@ -599,9 +603,11 @@ print_firewall(const struct ipt_entry *fw,
>  	IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
>  
>  	if (target) {
> -		if (target->print)
> +		if (target->print && t->u.user.revision == target->revision)
>  			/* Print the target information. */
>  			target->print(&fw->ip, t, format & FMT_NUMERIC);
> +		else if (target->print)
> +			printf(" %s%s", target->name, unsupported_rev);
>  	} else if (t->u.target_size != sizeof(*t))
>  		printf("[%u bytes of unknown target data] ",
>  		       (unsigned int)(t->u.target_size - sizeof(*t)));
> @@ -995,8 +1001,10 @@ static int print_match_save(const struct xt_entry_match *e,
>  			match->alias ? match->alias(e) : e->u.user.name);
>  
>  		/* some matches don't provide a save function */
> -		if (match->save)
> +		if (match->save && e->u.user.revision == match->revision)
>  			match->save(ip, e);
> +		else if (match->save)
> +			printf(unsupported_rev);
>  	} else {
>  		if (e->u.match_size) {
>  			fprintf(stderr,
> @@ -1095,8 +1103,10 @@ void print_rule4(const struct ipt_entry *e,
>  		}
>  
>  		printf(" -j %s", target->alias ? target->alias(t) : target_name);
> -		if (target->save)
> +		if (target->save && t->u.user.revision == target->revision)
>  			target->save(&e->ip, t);
> +		else if (target->save)
> +			printf(unsupported_rev);
>  		else {
>  			/* If the target size is greater than xt_entry_target
>  			 * there is something to be saved, we just don't know
> -- 
> 2.8.0.rc3.226.g39d4020
> 
> --
> To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

-
E-mail  : kadlec@blackhole.kfki.hu, kadlecsik.jozsef@wigner.mta.hu
PGP key : http://www.kfki.hu/~kadlec/pgp_public_key.txt
Address : Wigner Research Centre for Physics, Hungarian Academy of Sciences
          H-1525 Budapest 114, POB. 49, Hungary
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Willem de Bruijn April 21, 2017, 11:45 p.m. UTC | #3
On Fri, Apr 21, 2017 at 4:15 PM, Jozsef Kadlecsik
<kadlec@blackhole.kfki.hu> wrote:
> Hi,
>
> On Thu, 8 Dec 2016, Willem de Bruijn wrote:
>
>> From: Willem de Bruijn <willemb@google.com>
>>
>> Between revisions, the layout of xtables data may change completely.
>> Do not interpret the data in a revision M with a module of revision N.
>>
>> Signed-off-by: Willem de Bruijn <willemb@google.com>
>> ---
>>  iptables/ip6tables.c | 18 ++++++++++++++----
>>  iptables/iptables.c  | 18 ++++++++++++++----
>>  2 files changed, 28 insertions(+), 8 deletions(-)
>
> The patch breaks backward/forward compatibility in a match/target.
>
> When the list of the revisions of a given match/target of iptables is not
> exactly the same as for the kernel counter part (when the kernel module
> supports less revisions than iptables), then in spite of the supported
> match/target, " [unsupported revision]" is printed instead of the
> arguments. See https://bugzilla.netfilter.org/show_bug.cgi?id=1147.

Thanks for the report.

> Please consider reverting the patch. Or we should not stop in
> xtables_find_match/xtables_find_target at revision checking when the
> revision does not match, until all possibilities is not exhausted.

This seems like the better solution to me. The patch fixes a real issue
where garbage is printed by misinterpreting struct fields. Iptables should
try to lookup the matching revision for a match or target, instead of
returning the first one. I'll take a look.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Willem de Bruijn April 26, 2017, 9:15 p.m. UTC | #4
>> The patch breaks backward/forward compatibility in a match/target.
>>
>> When the list of the revisions of a given match/target of iptables is not
>> exactly the same as for the kernel counter part (when the kernel module
>> supports less revisions than iptables), then in spite of the supported
>> match/target, " [unsupported revision]" is printed instead of the
>> arguments. See https://bugzilla.netfilter.org/show_bug.cgi?id=1147.
>
> Thanks for the report.
>
>> Please consider reverting the patch. Or we should not stop in
>> xtables_find_match/xtables_find_target at revision checking when the
>> revision does not match, until all possibilities is not exhausted.
>
> This seems like the better solution to me. The patch fixes a real issue
> where garbage is printed by misinterpreting struct fields. Iptables should
> try to lookup the matching revision for a match or target, instead of
> returning the first one. I'll take a look.

I have a draft patch, but am so far unable to reproduce the problem.

It may be more subtle than what you describe. xtables_find_match
can call xtables_fully_register_pending_match which calls
compatible_match_revision to decide whether a match revision
is supported and, if multiple revisions are supported, which to prefer.

A simple setup where I add a new revision 2 of xt_mark to
iptables does not cause this problem. iptables correctly negotiates
with the kernel that the highest supported revision is 1.

Initially iptables tries to register rev 2. This succeeds. Then, it tries
to register rev 1. In xtables_fully_register_pending_match this
triggers branch if (old). The existing entry rev 2 is tested for
compatibility with the kernel in compatible_match_revision, which
fails. Rev 2 is then deleted and rev 1 inserted. Finally, it tries to
register rev 0. The branch is again entered, but now the call to
compatible_match_revision for rev 1 will succeed. As this has a
higher score, it is kept.

As a result, when logging a rule with iptables -L, the binary will
correctly identify the match of the highest level that the kernel
supports and prints that. It does not rev 2, so does not hit the
branch to print "[unsupported revision]" in this case.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Willem de Bruijn April 26, 2017, 11:44 p.m. UTC | #5
On Wed, Apr 26, 2017 at 5:15 PM, Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
>>> The patch breaks backward/forward compatibility in a match/target.
>>>
>>> When the list of the revisions of a given match/target of iptables is not
>>> exactly the same as for the kernel counter part (when the kernel module
>>> supports less revisions than iptables), then in spite of the supported
>>> match/target, " [unsupported revision]" is printed instead of the
>>> arguments. See https://bugzilla.netfilter.org/show_bug.cgi?id=1147.
>>
>> Thanks for the report.
>>
>>> Please consider reverting the patch. Or we should not stop in
>>> xtables_find_match/xtables_find_target at revision checking when the
>>> revision does not match, until all possibilities is not exhausted.
>>
>> This seems like the better solution to me. The patch fixes a real issue
>> where garbage is printed by misinterpreting struct fields. Iptables should
>> try to lookup the matching revision for a match or target, instead of
>> returning the first one. I'll take a look.
>
> I have a draft patch, but am so far unable to reproduce the problem.
>
> It may be more subtle than what you describe. xtables_find_match
> can call xtables_fully_register_pending_match which calls
> compatible_match_revision to decide whether a match revision
> is supported and, if multiple revisions are supported, which to prefer.

The case reported in bugzilla is for match 'set', which has not had a
new revision since v4 in 2014. Building the exact versions of iptables
and ipset reported there and disabling v4 support in the kernel correctly
reverts to using v3.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jozsef Kadlecsik April 27, 2017, 9:18 a.m. UTC | #6
Hi Willem,

On Wed, 26 Apr 2017, Willem de Bruijn wrote:

> On Wed, Apr 26, 2017 at 5:15 PM, Willem de Bruijn
> <willemdebruijn.kernel@gmail.com> wrote:
> >>> The patch breaks backward/forward compatibility in a match/target.
> >>>
> >>> When the list of the revisions of a given match/target of iptables is not
> >>> exactly the same as for the kernel counter part (when the kernel module
> >>> supports less revisions than iptables), then in spite of the supported
> >>> match/target, " [unsupported revision]" is printed instead of the
> >>> arguments. See https://bugzilla.netfilter.org/show_bug.cgi?id=1147.
> >>
> >> Thanks for the report.
> >>
> >>> Please consider reverting the patch. Or we should not stop in
> >>> xtables_find_match/xtables_find_target at revision checking when the
> >>> revision does not match, until all possibilities is not exhausted.
> >>
> >> This seems like the better solution to me. The patch fixes a real issue
> >> where garbage is printed by misinterpreting struct fields. Iptables should
> >> try to lookup the matching revision for a match or target, instead of
> >> returning the first one. I'll take a look.
> >
> > I have a draft patch, but am so far unable to reproduce the problem.
> >
> > It may be more subtle than what you describe. xtables_find_match
> > can call xtables_fully_register_pending_match which calls
> > compatible_match_revision to decide whether a match revision
> > is supported and, if multiple revisions are supported, which to prefer.
> 
> The case reported in bugzilla is for match 'set', which has not had a
> new revision since v4 in 2014. Building the exact versions of iptables
> and ipset reported there and disabling v4 support in the kernel correctly
> reverts to using v3.

Maybe the case can be reproduced with the following steps, but I'm 
guessing:

- rules inserted
- iptables binary downgraded/upgraded
- rules listed

Best regards,
Jozsef
-
E-mail  : kadlec@blackhole.kfki.hu, kadlecsik.jozsef@wigner.mta.hu
PGP key : http://www.kfki.hu/~kadlec/pgp_public_key.txt
Address : Wigner Research Centre for Physics, Hungarian Academy of Sciences
          H-1525 Budapest 114, POB. 49, Hungary
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Willem de Bruijn April 27, 2017, 11:33 a.m. UTC | #7
>> > It may be more subtle than what you describe. xtables_find_match
>> > can call xtables_fully_register_pending_match which calls
>> > compatible_match_revision to decide whether a match revision
>> > is supported and, if multiple revisions are supported, which to prefer.
>>
>> The case reported in bugzilla is for match 'set', which has not had a
>> new revision since v4 in 2014. Building the exact versions of iptables
>> and ipset reported there and disabling v4 support in the kernel correctly
>> reverts to using v3.
>
> Maybe the case can be reproduced with the following steps, but I'm
> guessing:
>
> - rules inserted
> - iptables binary downgraded/upgraded
> - rules listed

It will. This is largely what the patch protects against. But perhaps
it currently unnecessarily restricts binaries that support both
revisions N and N + 1 from printing structs of rev N, if those
are inserted from another binary that only supported up to N. Let
me try that.

Before this patch, a rule inserted with a binary that only supports
rev N for a given match, but read with another binary that supports
rev N+1 will result in the new binary incorrectly parsing a struct of
rev N as one of rev N + 1. And vice versa. See also the discussion in

  Re: [PATCH nf-next] netfilter: xt_bpf: support ebpf
  https://www.spinics.net/lists/netfilter-devel/msg45357.html

Casting structs in this way is unsafe. The downgrade path cannot
be fixed. But the upgrade path should. The way iptables negotiates
with the kernel and only loads the highest revision for any given
match or target may make this hard to change.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jozsef Kadlecsik April 27, 2017, 11:47 a.m. UTC | #8
On Thu, 27 Apr 2017, Willem de Bruijn wrote:

> > Maybe the case can be reproduced with the following steps, but I'm 
> > guessing:
> >
> > - rules inserted
> > - iptables binary downgraded/upgraded
> > - rules listed
> 
> It will. This is largely what the patch protects against. But perhaps
> it currently unnecessarily restricts binaries that support both
> revisions N and N + 1 from printing structs of rev N, if those
> are inserted from another binary that only supported up to N. Let
> me try that.
> 
> Before this patch, a rule inserted with a binary that only supports
> rev N for a given match, but read with another binary that supports
> rev N+1 will result in the new binary incorrectly parsing a struct of
> rev N as one of rev N + 1. And vice versa. See also the discussion in
> 
>   Re: [PATCH nf-next] netfilter: xt_bpf: support ebpf
>   https://www.spinics.net/lists/netfilter-devel/msg45357.html
> 
> Casting structs in this way is unsafe. The downgrade path cannot
> be fixed. But the upgrade path should. The way iptables negotiates
> with the kernel and only loads the highest revision for any given
> match or target may make this hard to change.

The downgrade case is OK and it is all right that iptables won't try to 
interpret the blob of the not supported revision and prints " [unsupported 
revision]" instead. But in the upgrade case iptables is able to print the 
target/match so it should do so.

Best regards,
Jozsef
-
E-mail  : kadlec@blackhole.kfki.hu, kadlecsik.jozsef@wigner.mta.hu
PGP key : http://www.kfki.hu/~kadlec/pgp_public_key.txt
Address : Wigner Research Centre for Physics, Hungarian Academy of Sciences
          H-1525 Budapest 114, POB. 49, Hungary
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
index c8d34e2..0d09181 100644
--- a/iptables/ip6tables.c
+++ b/iptables/ip6tables.c
@@ -76,6 +76,8 @@  static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
 static const char optflags[]
 = { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c'};
 
+static const char unsupported_rev[] = " [unsupported revision]";
+
 static struct option original_opts[] = {
 	{.name = "append",        .has_arg = 1, .val = 'A'},
 	{.name = "delete",        .has_arg = 1, .val = 'D'},
@@ -487,8 +489,10 @@  print_match(const struct xt_entry_match *m,
 		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
 
 	if (match) {
-		if (match->print)
+		if (match->print && m->u.user.revision == match->revision)
 			match->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
 		else
 			printf("%s ", match->name);
 	} else {
@@ -614,9 +618,11 @@  print_firewall(const struct ip6t_entry *fw,
 	IP6T_MATCH_ITERATE(fw, print_match, &fw->ipv6, format & FMT_NUMERIC);
 
 	if (target) {
-		if (target->print)
+		if (target->print && t->u.user.revision == target->revision)
 			/* Print the target information. */
 			target->print(&fw->ipv6, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
 	} else if (t->u.target_size != sizeof(*t))
 		printf("[%u bytes of unknown target data] ",
 		       (unsigned int)(t->u.target_size - sizeof(*t)));
@@ -1004,8 +1010,10 @@  static int print_match_save(const struct xt_entry_match *e,
 			match->alias ? match->alias(e) : e->u.user.name);
 
 		/* some matches don't provide a save function */
-		if (match->save)
+		if (match->save && e->u.user.revision == match->revision)
 			match->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
 	} else {
 		if (e->u.match_size) {
 			fprintf(stderr,
@@ -1104,8 +1112,10 @@  void print_rule6(const struct ip6t_entry *e,
 		}
 
 		printf(" -j %s", target->alias ? target->alias(t) : target_name);
-		if (target->save)
+		if (target->save && t->u.user.revision == target->revision)
 			target->save(&e->ipv6, t);
+		else if (target->save)
+			printf(unsupported_rev);
 		else {
 			/* If the target size is greater than xt_entry_target
 			 * there is something to be saved, we just don't know
diff --git a/iptables/iptables.c b/iptables/iptables.c
index 79fa37b..1bdde27 100644
--- a/iptables/iptables.c
+++ b/iptables/iptables.c
@@ -73,6 +73,8 @@  static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
 static const char optflags[]
 = { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'};
 
+static const char unsupported_rev[] = " [unsupported revision]";
+
 static struct option original_opts[] = {
 	{.name = "append",        .has_arg = 1, .val = 'A'},
 	{.name = "delete",        .has_arg = 1, .val = 'D'},
@@ -472,8 +474,10 @@  print_match(const struct xt_entry_match *m,
 		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
 
 	if (match) {
-		if (match->print)
+		if (match->print && m->u.user.revision == match->revision)
 			match->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
 		else
 			printf("%s ", match->name);
 	} else {
@@ -599,9 +603,11 @@  print_firewall(const struct ipt_entry *fw,
 	IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
 
 	if (target) {
-		if (target->print)
+		if (target->print && t->u.user.revision == target->revision)
 			/* Print the target information. */
 			target->print(&fw->ip, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
 	} else if (t->u.target_size != sizeof(*t))
 		printf("[%u bytes of unknown target data] ",
 		       (unsigned int)(t->u.target_size - sizeof(*t)));
@@ -995,8 +1001,10 @@  static int print_match_save(const struct xt_entry_match *e,
 			match->alias ? match->alias(e) : e->u.user.name);
 
 		/* some matches don't provide a save function */
-		if (match->save)
+		if (match->save && e->u.user.revision == match->revision)
 			match->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
 	} else {
 		if (e->u.match_size) {
 			fprintf(stderr,
@@ -1095,8 +1103,10 @@  void print_rule4(const struct ipt_entry *e,
 		}
 
 		printf(" -j %s", target->alias ? target->alias(t) : target_name);
-		if (target->save)
+		if (target->save && t->u.user.revision == target->revision)
 			target->save(&e->ip, t);
+		else if (target->save)
+			printf(unsupported_rev);
 		else {
 			/* If the target size is greater than xt_entry_target
 			 * there is something to be saved, we just don't know