After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 728791 - [review] fix warnings in libnm-glib for SetConfig
[review] fix warnings in libnm-glib for SetConfig
Status: RESOLVED FIXED
Product: NetworkManager
Classification: Platform
Component: general
git master
Other Linux
: Normal normal
: ---
Assigned To: NetworkManager maintainer(s)
NetworkManager maintainer(s)
Depends on:
Blocks:
 
 
Reported: 2014-04-23 13:29 UTC by Thomas Haller
Modified: 2014-06-06 14:28 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Patch v1, 0001 (2.76 KB, patch)
2014-04-23 13:29 UTC, Thomas Haller
reviewed Details | Review
Patch v1, 0002 (2.20 KB, patch)
2014-04-23 13:30 UTC, Thomas Haller
needs-work Details | Review
[PATCH] rejected commit to check GValue type in libnm-glib (2.39 KB, patch)
2014-06-04 18:44 UTC, Thomas Haller
rejected Details | Review

Description Thomas Haller 2014-04-23 13:29:04 UTC
Please review the attached patches.

To quote the commit messages:

1)

    libnm-glib: fix resetting GValue type when receiving VPN config (SetConfig)
    
    When receiving updates with VPN config, the library overwrites the already 
    initialized GValue fields, by calling g_value_init() again. This is an 
    error and causes the following warning:
    
       (nm-openvpn-service:27645): GLib-GObject-WARNING **: gvalue.c:183: cannot initialize GValue with type gchararray, the value has already been initialized as gchararray



2)

    libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)
    
    When accepting the values, check for the proper GType and g_warn
    on error.
Comment 1 Thomas Haller 2014-04-23 13:29:49 UTC
Created attachment 274945 [details] [review]
Patch v1, 0001
Comment 2 Thomas Haller 2014-04-23 13:30:07 UTC
Created attachment 274946 [details] [review]
Patch v1, 0002
Comment 3 Dan Winship 2014-04-23 14:57:43 UTC
Comment on attachment 274945 [details] [review]
Patch v1, 0001

>+_g_value_set (GValue *dst, GValue *src)
>+{
>+	if (src) {

You need an else clause here, to clear dst if src was NULL.

>+		if (!G_IS_VALUE (dst))
>+			g_value_init (dst, type);
>+		else if (G_VALUE_TYPE (dst) != type) {
>+			g_value_unset (dst);
>+			g_value_init (dst, type);
>+		} else
>+			g_value_reset (dst);

You can simplify this to:

		if (G_IS_VALUE (dst))
			g_value_unset (dst);
		g_value_init (dst, type);
Comment 4 Dan Winship 2014-04-23 15:00:31 UTC
Comment on attachment 274946 [details] [review]
Patch v1, 0002

>When accepting the values, check for the proper GType and g_warn
>on error.

Why? Doesn't gvalue already do that?

>+		if (type != G_VALUE_TYPE (src)) {
>+			g_warn_if_reached ();
>+			return;
>+		}

g_return_if_fail (type == G_VALUE_TYPE (src));
Comment 5 Thomas Haller 2014-04-23 16:46:44 UTC
(In reply to comment #3)
> (From update of attachment 274945 [details] [review])
> >+_g_value_set (GValue *dst, GValue *src)
> >+{
> >+	if (src) {
> 
> You need an else clause here, to clear dst if src was NULL.

you mean

 if (src) {
    ...
 } else
     g_value_unset (dst);

? 

This would actually change the behaviour to what it was before...

Before

-	val = g_hash_table_lookup (config, NM_VPN_PLUGIN_CONFIG_MTU);
-	if (val) {
-		g_value_init (&priv->mtu, G_VALUE_TYPE (val));
-		g_value_copy (val, &priv->mtu);
-	}

it would just skip over missing values (leaving the previous value).


> >+		if (!G_IS_VALUE (dst))
> >+			g_value_init (dst, type);
> >+		else if (G_VALUE_TYPE (dst) != type) {
> >+			g_value_unset (dst);
> >+			g_value_init (dst, type);
> >+		} else
> >+			g_value_reset (dst);
> 
> You can simplify this to:
> 
>         if (G_IS_VALUE (dst))
>             g_value_unset (dst);
>         g_value_init (dst, type);

true
Comment 6 Thomas Haller 2014-04-23 16:51:25 UTC
(In reply to comment #4)
> (From update of attachment 274946 [details] [review])
> >When accepting the values, check for the proper GType and g_warn
> >on error.
> 
> Why? Doesn't gvalue already do that?

really? If you would a wrong type, then it would stay in the hashset. Maybe the error would be noted later or maybe not (looking at src/vpn-manager/nm-vpn-connection.c:816)

It makes sense to check for wrong type at an early place.



> >+		if (type != G_VALUE_TYPE (src)) {
> >+			g_warn_if_reached ();
> >+			return;
> >+		}
> 
> g_return_if_fail (type == G_VALUE_TYPE (src));

Hm, I wanted to g_warn, instead of g_return (which is less critical)...
Comment 7 Dan Winship 2014-04-23 18:33:57 UTC
(In reply to comment #5)
> This would actually change the behaviour to what it was before...

> it would just skip over missing values (leaving the previous value).

But that code was written assuming that set_config() was only going to be called once (hence the failure to g_value_unset()).

And if you look at what that code does, it's just to make sure that certain values from Config get copied into Ip4Config as well; so if the new Config hash doesn't include that value, then the new Ip4Config hash shouldn't either.

(In reply to comment #6)
> > g_return_if_fail (type == G_VALUE_TYPE (src));
> 
> Hm, I wanted to g_warn, instead of g_return (which is less critical)...

g_return_if_fail() is a g_critical(), which is more critical than g_warn*
Comment 8 Thomas Haller 2014-04-23 19:12:21 UTC
(In reply to comment #7)
> (In reply to comment #5)
> > This would actually change the behaviour to what it was before...
> 
> > it would just skip over missing values (leaving the previous value).
> 
> But that code was written assuming that set_config() was only going to be
> called once (hence the failure to g_value_unset()).
> 
> And if you look at what that code does, it's just to make sure that certain
> values from Config get copied into Ip4Config as well; so if the new Config hash
> doesn't include that value, then the new Ip4Config hash shouldn't either.

I could reproduce this warning easily with the openvpn plugin by sending -USR1 to the openvpn process. So, maybe there is a bug there.

Are you sure that the assumption holds?


> (In reply to comment #6)
> > > g_return_if_fail (type == G_VALUE_TYPE (src));
> > 
> > Hm, I wanted to g_warn, instead of g_return (which is less critical)...
> 
> g_return_if_fail() is a g_critical(), which is more critical than g_warn*

Oh yes. I mean, I wanted only to warn, not critically.
Comment 9 Dan Winship 2014-04-24 16:58:58 UTC
(In reply to comment #8)
> I could reproduce this warning easily with the openvpn plugin by sending -USR1
> to the openvpn process. So, maybe there is a bug there.

We already *know* there's a bug there; it's the bug that you're fixing in this patch. But you're only fixing part of it.

> Are you sure that the assumption holds?

As the person who wrote the code in question, yes.
Comment 10 Thomas Haller 2014-05-05 20:27:36 UTC
Reworked both patches and pushed to th/bgo728791_vpn_SetConfig.

@Dan, could you take another look?


Note, that now the case of resetting a property a second time again causes a g_warn(), but the value gets unset.



(In reply to comment #9)
> (In reply to comment #8)
> > I could reproduce this warning easily with the openvpn plugin by sending -USR1
> > to the openvpn process. So, maybe there is a bug there.
> 
> We already *know* there's a bug there; it's the bug that you're fixing in this
> patch. But you're only fixing part of it.

You mean, the other part of the bug is in nm-openvpn?
Comment 11 Dan Williams 2014-05-05 21:19:07 UTC
(In reply to comment #10)
> Reworked both patches and pushed to th/bgo728791_vpn_SetConfig.
> 
> @Dan, could you take another look?
> 
> 
> Note, that now the case of resetting a property a second time again causes a
> g_warn(), but the value gets unset.

A clearer explanation would be:

"If a new value is empty/not-given but was previously given/not-empty, a warning will be raised."

but the code allows you to overwrite a previously set value with a new non-empty value without a warning.

It took me a minute here to realize that this branch is really about ensuring that things work when SetConfig() is called by the VPN plugin helper (that gets executed by vpnc/openvpn/etc when the VPN connect is successful) a *second* or later time, like with Thomas' nm-openvpn fixes for up-restart.  Might be worth clarifying that in the commit message, like:

----
libnm-glib-vpn: fix resetting IP config during additional SetConfig() calls

When receiving updated VPN IP configuration from the helper after the initial
connect event, the library overwrites the already initialized GValue fields
by calling g_value_init() again. This is an error and causes the following
warning:

(nm-openvpn-service:27645): GLib-GObject-WARNING **: gvalue.c:183: cannot initialize GValue with type gchararray, the value has already been initialized as gchararray
----
Comment 12 Thomas Haller 2014-05-06 13:30:35 UTC
I still don't understand, is this a bug of the user/caller (nm-openvpn)?

[comment #7]
  >> But that code was written assuming that set_config() was only going to be
  >> called once (hence the failure to g_value_unset()).


In that case the caller must be fixed and we should g_warn always also in case
of [comment #11]
  >> but the code allows you to overwrite a previously set value with a new
  >> non-empty value without a warning.


Otherwise, no g_warn should be raised, because the user did nothing wrong.
Comment 13 Dan Winship 2014-05-06 14:02:30 UTC
There is one bug: the change that was made to nm_vpn_plugin_set_config() and nm_vpn_plugin_set_ip4_config() in 960c1ae8 implicitly assumed that they could each only ever be called once in any given process. Which is not true.

There are two manifestations of this bug: (1) if the second (or third, etc) call to nm_vpn_plugin_set_config() tries to update banner, tundev, gateway, or mtu, then there will be a g_warning because it will try to re-g_value_init() a GValue that is already initialized. (2) if the second (third, etc) call to nm_vpn_plugin_set_config() *doesn't* update banner, tundev, gateway, or mtu, then the next call to nm_vpn_plugin_set_ip4_config() will copy the OLD values of those properties from the earlier nm_vpn_plugin_set_config() call into the hash table emitted in the IP4_CONFIG signal, when it actually should be leaving them unset, like they were in the corresponding CONFIG signal.
Comment 14 Thomas Haller 2014-05-06 14:18:09 UTC
Understood.

Repushed (non-ff).

Reworded commit message according to comment 11, and added two fixup commits.
Comment 15 Dan Williams 2014-05-07 04:01:23 UTC
Hmm, the latest code in nm_vpn_plugin_set_config() will leave a previous priv->gateway if the (second/third/etc) call doesn't pass a gateway.  It should g_clear_value(&priv->gateway); if NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY isn't passed in the hash.
Comment 16 Thomas Haller 2014-05-07 11:41:28 UTC
(In reply to comment #15)
> Hmm, the latest code in nm_vpn_plugin_set_config() will leave a previous
> priv->gateway if the (second/third/etc) call doesn't pass a gateway.  It should
> g_clear_value(&priv->gateway); if NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY isn't passed
> in the hash.

Rebased fixup which fixes this.

Two additional fixup commits change to code to use variadic arguments. I think it's nicer and shorter.
Comment 17 Dan Winship 2014-05-07 18:19:11 UTC
The first commit and its fixup (which mistakenly references the second commit rather than the first) look good.

I don't think we need the second commit; we'll already get warnings in the daemon anyway if the VPN helper sends the wrong types (since, eg, g_value_get_string() will spew if !G_VALUE_HOLDS_STRING()). And it's weird to check the types of 4 of the properties, but not all the other ones...
Comment 18 Thomas Haller 2014-05-12 16:00:36 UTC
(In reply to comment #17)
> The first commit and its fixup (which mistakenly references the second commit
> rather than the first) look good.
> 
> I don't think we need the second commit; we'll already get warnings in the
> daemon anyway if the VPN helper sends the wrong types (since, eg,
> g_value_get_string() will spew if !G_VALUE_HOLDS_STRING()). And it's weird to
> check the types of 4 of the properties, but not all the other ones...

It will, unless glib is compiled with G_DISABLE_CHECKS.
I think, if NM allows a g_return_*() to happen, it is a bug and not something that ever should happen on user-input.

E.g. see src/vpn-manager/nm-vpn-connection.c, process_generic_config().

Is this worth fixing too? I pushed a commit on top of th/bgo728791_vpn_SetConfig, which catches some of these cases and fixes some leaks.


I think the second commit is useful, because it produces a g_warning early on, when the user does the wrong thing, not only later in the deamon. The deamon anyway has to do any validation independently. I don't mind the mentioned weirdness :)
Comment 19 Dan Winship 2014-05-16 15:12:38 UTC
(In reply to comment #18)
> (In reply to comment #17)
> > I don't think we need the second commit; we'll already get warnings in the
> > daemon anyway if the VPN helper sends the wrong types (since, eg,
> > g_value_get_string() will spew if !G_VALUE_HOLDS_STRING()). And it's weird to
> > check the types of 4 of the properties, but not all the other ones...
> 
> It will, unless glib is compiled with G_DISABLE_CHECKS.
> I think, if NM allows a g_return_*() to happen, it is a bug

(You don't ever need to think about G_DISABLE_CHECKS. As you say, when the condition checked by a g_return_*() is FALSE, then that indicates a bug. But it's a bug whether G_DISABLE_CHECKS is defined or not, so there is no reason to include G_DISABLE_CHECKS in your reasoning.)

> and not something that ever should happen on user-input.

Note that there is no user input here; the data is all generated by the VPN service helper program, based on data it receives from the VPN command-line program. So, if the data types are wrong, it *does* indicate a bug (in, eg, nm-openvpn-service-helper).

(Arguably, return-if-fails probably shouldn't occur across process boundaries though. But in that case, nm-vpn-connection still needs to check the data types no matter what nm-vpn-plugin does, because they are in different processes as well.)

> I think the second commit is useful, because it produces a g_warning early on,
> when the user does the wrong thing, not only later in the deamon. The deamon
> anyway has to do any validation independently. I don't mind the mentioned
> weirdness :)

Again, it's not "the user does the wrong thing", it's "the service helper program has a bug in it". And it's not "early on", because the data gets passed to the daemon almost immediately after it's received by the plugin. And at any rate, you are type-checking 4 of the arguments, but leaving the other 23 un-checked, and not checking that all of the arguments passed in are actually defined, and not checking that the mandatory arguments are present, etc.

And there's no reason for NMVpnPlugin to do any of that; it just receives a hash, and passes it on. NMVpnConnection is what actually interprets the hash, and knows what information does and doesn't need to be there, so that's where it makes sense to check it.
Comment 20 Thomas Haller 2014-05-21 10:33:30 UTC
(In reply to comment #19)
> (In reply to comment #18)
> > (In reply to comment #17)

> > I think the second commit is useful, because it produces a g_warning early on,
> > when the user does the wrong thing, not only later in the deamon. The deamon
> > anyway has to do any validation independently. I don't mind the mentioned
> > weirdness :)
> 
> Again, it's not "the user does the wrong thing", it's "the service helper
> program has a bug in it". And it's not "early on", because the data gets passed
> to the daemon almost immediately after it's received by the plugin. And at any
> rate, you are type-checking 4 of the arguments, but leaving the other 23
> un-checked, and not checking that all of the arguments passed in are actually
> defined, and not checking that the mandatory arguments are present, etc.
> 
> And there's no reason for NMVpnPlugin to do any of that; it just receives a
> hash, and passes it on. NMVpnConnection is what actually interprets the hash,
> and knows what information does and doesn't need to be there, so that's where
> it makes sense to check it.

The commits adds some validation to libnm-glib. So, it is closer to the "consumer" of the library who does something wrong -- I see the word "user" is confusing here.

It might not be the earliest point where the library could validate anything, but it's relatively "early-on", and still at the place where the consumer tries to set the wrong value -- contrary to a validation failure one process-boundary away.

Anyway, we can also not validate in the library, let's just drop the commit
(I just pushed a new commit "fixup! libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)", to fix a bug there).



I pushed another commit:

  "vpn: cleanup receiving VPN parameters and check for GValue types"
Comment 21 Dan Williams 2014-06-04 04:50:03 UTC
> libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)

Instead of "(GType) 0" how about G_TYPE_INVALID?

Since we're always expecting at least one GType, is the first outer "if (type != G_TYPE_INVALID)" useful?  Wouldn't this be simpler and achieve the same result?

		va_start (ap, src);
		type = va_arg (ap, GType);
		while (type != G_TYPE_INVALID) {
			if (type == src_type)
				goto valid_type;
			type = va_arg (ap, GType);
		}
		g_warn_if_reached ();
valid_type:
		va_end (ap);


> vpn: cleanup receiving VPN parameters and check for GValue types

I'd save some horizontal space and

#define INVALID_ARG_FMT "VPN connection '%s' has invalid argument %s"
#define INVALID_ARG_ARGS(key) nm_connection_get_id (priv->connection),key

nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT, INVALID_ARG_ARGS(NM_VPN_PLUGIN_CONFIG_BANNER));

Though that looks kinda ugly with all the capitals everywhere.  So maybe just:

nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT, nm_connection_get_id (priv->connection), , NM_VPN_PLUGIN_CONFIG_BANNER);
Comment 22 Jiri Klimes 2014-06-04 10:19:52 UTC
static void
__g_value_set (GValue *dst, GValue *src, ...)
#define _g_value_set(dst, src, ...)    __g_value_set (dst, src, __VA_ARGS__, (GType) 0);
{

I'd move the #define above the function definition and remove the trailing semicolon.
Comment 23 Thomas Haller 2014-06-04 11:47:43 UTC
(In reply to comment #21)
> > libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)
> 
> Instead of "(GType) 0" how about G_TYPE_INVALID?

Done


> Since we're always expecting at least one GType, is the first outer "if (type
> != G_TYPE_INVALID)" useful?  Wouldn't this be simpler and achieve the same
> result?
> 
>         va_start (ap, src);
>         type = va_arg (ap, GType);
>         while (type != G_TYPE_INVALID) {
>             if (type == src_type)
>                 goto valid_type;
>             type = va_arg (ap, GType);
>         }
>         g_warn_if_reached ();
> valid_type:
>         va_end (ap);

Possible, which saves two LOC. But then it always needs at least one type argument (which currently is indeed always the case). But the function was also useful beyond the type-checking... I would keep it.


> > vpn: cleanup receiving VPN parameters and check for GValue types
> 
> I'd save some horizontal space and
> 
> #define INVALID_ARG_FMT "VPN connection '%s' has invalid argument %s"
> #define INVALID_ARG_ARGS(key) nm_connection_get_id (priv->connection),key
> 
> nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT,
> INVALID_ARG_ARGS(NM_VPN_PLUGIN_CONFIG_BANNER));
> 
> Though that looks kinda ugly with all the capitals everywhere.  So maybe just:
> 
> nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT, nm_connection_get_id (priv->connection),
> , NM_VPN_PLUGIN_CONFIG_BANNER);

I solved it a bit differently, 

+#define LOG_INVALID_ARG(property) \
+    nm_log_dbg (LOGD_VPN, "VPN connection '%s' has invalid argument %s", \
+                          nm_connection_get_id (priv->connection), property)

how is that?




(In reply to comment #22)
> static void
> __g_value_set (GValue *dst, GValue *src, ...)
> #define _g_value_set(dst, src, ...)    __g_value_set (dst, src, __VA_ARGS__,
> (GType) 0);
> {
> 
> I'd move the #define above the function definition and remove the trailing
> semicolon.

Done





How about danw's suggestion to completely drop commit
  "libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)"
?
Comment 24 Dan Williams 2014-06-04 15:49:24 UTC
(In reply to comment #23)
> > > vpn: cleanup receiving VPN parameters and check for GValue types
> > 
> > I'd save some horizontal space and
> > 
> > #define INVALID_ARG_FMT "VPN connection '%s' has invalid argument %s"
> > #define INVALID_ARG_ARGS(key) nm_connection_get_id (priv->connection),key
> > 
> > nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT,
> > INVALID_ARG_ARGS(NM_VPN_PLUGIN_CONFIG_BANNER));
> > 
> > Though that looks kinda ugly with all the capitals everywhere.  So maybe just:
> > 
> > nm_log_dbg (LOGD_VPN, INVALID_ARG_FMT, nm_connection_get_id (priv->connection),
> > , NM_VPN_PLUGIN_CONFIG_BANNER);
> 
> I solved it a bit differently, 
> 
> +#define LOG_INVALID_ARG(property) \
> +    nm_log_dbg (LOGD_VPN, "VPN connection '%s' has invalid argument %s", \
> +                          nm_connection_get_id (priv->connection), property)
> 
> how is that?

Looks fine to me.

> How about danw's suggestion to completely drop commit
>   "libnm-glib: check GValue type in vpn_plugin_set_config (SetConfig)"

Yeah, he does have a point... it's data coming from a root process, and it's programmer error if the types are wrong (since they are specified in the NetworkManagerVPN.h header too).  So I guess we don't need to validate them there, just in nm-vpn-connection.c.
Comment 25 Thomas Haller 2014-06-04 18:43:01 UTC
Merged two commits as

http://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=f53a35e030cb7b03515a8470f81a9d10ff72179e

and dropped the other one.
Comment 26 Thomas Haller 2014-06-04 18:44:24 UTC
Created attachment 277896 [details] [review]
[PATCH] rejected commit to check GValue type in libnm-glib

For the record, here the rejected patch