diff mbox series

Do not use %m in common code to print error messages

Message ID 20191018104438.6158-1-thuth@redhat.com
State New
Headers show
Series Do not use %m in common code to print error messages | expand

Commit Message

Thomas Huth Oct. 18, 2019, 10:44 a.m. UTC
The %m format specifier is an extension from glibc - and when compiling
QEMU for NetBSD, the compiler correctly complains, e.g.:

/home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c: In function 'sigfd_handler':
/home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c:64:13: warning: %m is only
 allowed in syslog(3) like functions [-Wformat=]
             printf("read from sigfd returned %zd: %m\n", len);
             ^
Let's use g_strerror() here instead, which is an easy-to-use wrapper
around the thread-safe strerror_r() function.

While we're at it, also convert the "printf()" in main-loop.c into
the preferred "error_report()".

Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 hw/misc/tmp421.c | 8 ++++++--
 util/main-loop.c | 4 +++-
 util/systemd.c   | 5 +++--
 3 files changed, 12 insertions(+), 5 deletions(-)

Comments

Daniel P. Berrangé Oct. 18, 2019, 10:57 a.m. UTC | #1
On Fri, Oct 18, 2019 at 12:44:38PM +0200, Thomas Huth wrote:
> The %m format specifier is an extension from glibc - and when compiling
> QEMU for NetBSD, the compiler correctly complains, e.g.:
> 
> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c: In function 'sigfd_handler':
> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c:64:13: warning: %m is only
>  allowed in syslog(3) like functions [-Wformat=]
>              printf("read from sigfd returned %zd: %m\n", len);
>              ^
> Let's use g_strerror() here instead, which is an easy-to-use wrapper
> around the thread-safe strerror_r() function.
> 
> While we're at it, also convert the "printf()" in main-loop.c into
> the preferred "error_report()".
> 
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
>  hw/misc/tmp421.c | 8 ++++++--
>  util/main-loop.c | 4 +++-
>  util/systemd.c   | 5 +++--
>  3 files changed, 12 insertions(+), 5 deletions(-)
> 
> diff --git a/hw/misc/tmp421.c b/hw/misc/tmp421.c
> index 9f044705fa..f23c46a40a 100644
> --- a/hw/misc/tmp421.c
> +++ b/hw/misc/tmp421.c
> @@ -120,7 +120,9 @@ static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
>      int tempid;
>  
>      if (sscanf(name, "temperature%d", &tempid) != 1) {
> -        error_setg(errp, "error reading %s: %m", name);
> +        const char *errmsg = g_strerror(errno);
> +        error_setg(errp, "error reading %s: %s", name, errmsg);
> +        g_free((gpointer)errmsg);

Kaboom crash. This is trying to free a const string that is the caller
doesn't own. It remains under ownership of g_strerror forever.

Regards,
Daniel
Thomas Huth Oct. 18, 2019, 11:31 a.m. UTC | #2
On 18/10/2019 12.57, Daniel P. Berrangé wrote:
> On Fri, Oct 18, 2019 at 12:44:38PM +0200, Thomas Huth wrote:
>> The %m format specifier is an extension from glibc - and when compiling
>> QEMU for NetBSD, the compiler correctly complains, e.g.:
>>
>> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c: In function 'sigfd_handler':
>> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c:64:13: warning: %m is only
>>  allowed in syslog(3) like functions [-Wformat=]
>>              printf("read from sigfd returned %zd: %m\n", len);
>>              ^
>> Let's use g_strerror() here instead, which is an easy-to-use wrapper
>> around the thread-safe strerror_r() function.
>>
>> While we're at it, also convert the "printf()" in main-loop.c into
>> the preferred "error_report()".
>>
>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>> ---
>>  hw/misc/tmp421.c | 8 ++++++--
>>  util/main-loop.c | 4 +++-
>>  util/systemd.c   | 5 +++--
>>  3 files changed, 12 insertions(+), 5 deletions(-)
>>
>> diff --git a/hw/misc/tmp421.c b/hw/misc/tmp421.c
>> index 9f044705fa..f23c46a40a 100644
>> --- a/hw/misc/tmp421.c
>> +++ b/hw/misc/tmp421.c
>> @@ -120,7 +120,9 @@ static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
>>      int tempid;
>>  
>>      if (sscanf(name, "temperature%d", &tempid) != 1) {
>> -        error_setg(errp, "error reading %s: %m", name);
>> +        const char *errmsg = g_strerror(errno);
>> +        error_setg(errp, "error reading %s: %s", name, errmsg);
>> +        g_free((gpointer)errmsg);
> 
> Kaboom crash. This is trying to free a const string that is the caller
> doesn't own. It remains under ownership of g_strerror forever.

Well, if you look at the implementation of g_strerror(), you can see
that glib returns allocated memory here (with g_strdup() for example).
So if we don't free, this will leak memory over time.

But you're right, the "const" and documentation of the function indicate
that the caller should rather not free the memory. What a bummer, sounds
like g_strerror() is pretty much useless unless you immediately exit()
afterwards (and you care about not leaking memory).

I guess we have to implement our own wrapper, qemu_strerror() instead?

 Thomas
Daniel P. Berrangé Oct. 18, 2019, 11:37 a.m. UTC | #3
On Fri, Oct 18, 2019 at 01:31:34PM +0200, Thomas Huth wrote:
> On 18/10/2019 12.57, Daniel P. Berrangé wrote:
> > On Fri, Oct 18, 2019 at 12:44:38PM +0200, Thomas Huth wrote:
> >> The %m format specifier is an extension from glibc - and when compiling
> >> QEMU for NetBSD, the compiler correctly complains, e.g.:
> >>
> >> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c: In function 'sigfd_handler':
> >> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c:64:13: warning: %m is only
> >>  allowed in syslog(3) like functions [-Wformat=]
> >>              printf("read from sigfd returned %zd: %m\n", len);
> >>              ^
> >> Let's use g_strerror() here instead, which is an easy-to-use wrapper
> >> around the thread-safe strerror_r() function.
> >>
> >> While we're at it, also convert the "printf()" in main-loop.c into
> >> the preferred "error_report()".
> >>
> >> Signed-off-by: Thomas Huth <thuth@redhat.com>
> >> ---
> >>  hw/misc/tmp421.c | 8 ++++++--
> >>  util/main-loop.c | 4 +++-
> >>  util/systemd.c   | 5 +++--
> >>  3 files changed, 12 insertions(+), 5 deletions(-)
> >>
> >> diff --git a/hw/misc/tmp421.c b/hw/misc/tmp421.c
> >> index 9f044705fa..f23c46a40a 100644
> >> --- a/hw/misc/tmp421.c
> >> +++ b/hw/misc/tmp421.c
> >> @@ -120,7 +120,9 @@ static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
> >>      int tempid;
> >>  
> >>      if (sscanf(name, "temperature%d", &tempid) != 1) {
> >> -        error_setg(errp, "error reading %s: %m", name);
> >> +        const char *errmsg = g_strerror(errno);
> >> +        error_setg(errp, "error reading %s: %s", name, errmsg);
> >> +        g_free((gpointer)errmsg);
> > 
> > Kaboom crash. This is trying to free a const string that is the caller
> > doesn't own. It remains under ownership of g_strerror forever.
> 
> Well, if you look at the implementation of g_strerror(), you can see
> that glib returns allocated memory here (with g_strdup() for example).
> So if we don't free, this will leak memory over time.

This is *not* a leak, it is a cache.

It is maintaining a static hash table that caches the mapping from
errno -> string. So many calls all with the same errno value will
return the same cached string. So by free'ing the string you're
causing a use-after-free flaw the next time the same errno is
passed to g_strerror. The memory usage here is finite, bounded
by the number of possible errnos that we see.

> But you're right, the "const" and documentation of the function indicate
> that the caller should rather not free the memory. What a bummer, sounds
> like g_strerror() is pretty much useless unless you immediately exit()
> afterwards (and you care about not leaking memory).
> 
> I guess we have to implement our own wrapper, qemu_strerror() instead?

No, g_strerror is working fine without free'ing the result.

Regards,
Daniel
Thomas Huth Oct. 18, 2019, 11:40 a.m. UTC | #4
On 18/10/2019 13.37, Daniel P. Berrangé wrote:
> On Fri, Oct 18, 2019 at 01:31:34PM +0200, Thomas Huth wrote:
>> On 18/10/2019 12.57, Daniel P. Berrangé wrote:
>>> On Fri, Oct 18, 2019 at 12:44:38PM +0200, Thomas Huth wrote:
>>>> The %m format specifier is an extension from glibc - and when compiling
>>>> QEMU for NetBSD, the compiler correctly complains, e.g.:
>>>>
>>>> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c: In function 'sigfd_handler':
>>>> /home/qemu/qemu-test.ELjfrQ/src/util/main-loop.c:64:13: warning: %m is only
>>>>  allowed in syslog(3) like functions [-Wformat=]
>>>>              printf("read from sigfd returned %zd: %m\n", len);
>>>>              ^
>>>> Let's use g_strerror() here instead, which is an easy-to-use wrapper
>>>> around the thread-safe strerror_r() function.
>>>>
>>>> While we're at it, also convert the "printf()" in main-loop.c into
>>>> the preferred "error_report()".
>>>>
>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>> ---
>>>>  hw/misc/tmp421.c | 8 ++++++--
>>>>  util/main-loop.c | 4 +++-
>>>>  util/systemd.c   | 5 +++--
>>>>  3 files changed, 12 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/hw/misc/tmp421.c b/hw/misc/tmp421.c
>>>> index 9f044705fa..f23c46a40a 100644
>>>> --- a/hw/misc/tmp421.c
>>>> +++ b/hw/misc/tmp421.c
>>>> @@ -120,7 +120,9 @@ static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
>>>>      int tempid;
>>>>  
>>>>      if (sscanf(name, "temperature%d", &tempid) != 1) {
>>>> -        error_setg(errp, "error reading %s: %m", name);
>>>> +        const char *errmsg = g_strerror(errno);
>>>> +        error_setg(errp, "error reading %s: %s", name, errmsg);
>>>> +        g_free((gpointer)errmsg);
>>>
>>> Kaboom crash. This is trying to free a const string that is the caller
>>> doesn't own. It remains under ownership of g_strerror forever.
>>
>> Well, if you look at the implementation of g_strerror(), you can see
>> that glib returns allocated memory here (with g_strdup() for example).
>> So if we don't free, this will leak memory over time.
> 
> This is *not* a leak, it is a cache.
> 
> It is maintaining a static hash table that caches the mapping from
> errno -> string. So many calls all with the same errno value will
> return the same cached string. So by free'ing the string you're
> causing a use-after-free flaw the next time the same errno is
> passed to g_strerror. The memory usage here is finite, bounded
> by the number of possible errnos that we see.
> 
>> But you're right, the "const" and documentation of the function indicate
>> that the caller should rather not free the memory. What a bummer, sounds
>> like g_strerror() is pretty much useless unless you immediately exit()
>> afterwards (and you care about not leaking memory).
>>
>> I guess we have to implement our own wrapper, qemu_strerror() instead?
> 
> No, g_strerror is working fine without free'ing the result.

Ah, well, thanks for the explanation, not sure how I could have missed
that when looking at the source code :-/ Time for weekend, I guess...

So g_strerror should be fine, and I'll respin the patch without g_free.

 Thomas
no-reply@patchew.org Oct. 18, 2019, 11:29 p.m. UTC | #5
Patchew URL: https://patchew.org/QEMU/20191018104438.6158-1-thuth@redhat.com/



Hi,

This series failed the docker-quick@centos7 build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#!/bin/bash
make docker-image-centos7 V=1 NETWORK=1
time make docker-test-quick@centos7 SHOW_ENV=1 J=14 NETWORK=1
=== TEST SCRIPT END ===

  CC      hw/misc/imx6_ccm.o
  CC      hw/misc/imx6ul_ccm.o
/tmp/qemu-test/src/hw/misc/tmp421.c: In function 'tmp421_set_temperature':
/tmp/qemu-test/src/hw/misc/tmp421.c:166:9: error: format '%s' expects a matching 'char *' argument [-Werror=format=]
         error_setg(errp, "error reading %s: %s", errmsg);
         ^
cc1: all warnings being treated as errors
  CC      hw/misc/imx7_ccm.o
  CC      hw/misc/imx2_wdt.o
make: *** [hw/misc/tmp421.o] Error 1
make: *** Waiting for unfinished jobs....
Traceback (most recent call last):
  File "./tests/docker/docker.py", line 662, in <module>
---
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['sudo', '-n', 'docker', 'run', '--label', 'com.qemu.instance.uuid=eae0544322904584b30d3ce7bcb77a82', '-u', '1001', '--security-opt', 'seccomp=unconfined', '--rm', '-e', 'TARGET_LIST=', '-e', 'EXTRA_CONFIGURE_OPTS=', '-e', 'V=', '-e', 'J=14', '-e', 'DEBUG=', '-e', 'SHOW_ENV=1', '-e', 'CCACHE_DIR=/var/tmp/ccache', '-v', '/home/patchew/.cache/qemu-docker-ccache:/var/tmp/ccache:z', '-v', '/var/tmp/patchew-tester-tmp-786nsriq/src/docker-src.2019-10-18-19.26.59.10681:/var/tmp/qemu:z,ro', 'qemu:centos7', '/var/tmp/qemu/run', 'test-quick']' returned non-zero exit status 2.
filter=--filter=label=com.qemu.instance.uuid=eae0544322904584b30d3ce7bcb77a82
make[1]: *** [docker-run] Error 1
make[1]: Leaving directory `/var/tmp/patchew-tester-tmp-786nsriq/src'
make: *** [docker-run-test-quick@centos7] Error 2

real    2m5.578s
user    0m7.522s


The full log is available at
http://patchew.org/logs/20191018104438.6158-1-thuth@redhat.com/testing.docker-quick@centos7/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
no-reply@patchew.org Oct. 18, 2019, 11:31 p.m. UTC | #6
Patchew URL: https://patchew.org/QEMU/20191018104438.6158-1-thuth@redhat.com/



Hi,

This series failed the docker-mingw@fedora build test. Please find the testing commands and
their output below. If you have Docker installed, you can probably reproduce it
locally.

=== TEST SCRIPT BEGIN ===
#! /bin/bash
export ARCH=x86_64
make docker-image-fedora V=1 NETWORK=1
time make docker-test-mingw@fedora J=14 NETWORK=1
=== TEST SCRIPT END ===

  CC      hw/ssi/pl022.o
In file included from /tmp/qemu-test/src/hw/misc/tmp421.c:30:
/tmp/qemu-test/src/hw/misc/tmp421.c: In function 'tmp421_set_temperature':
/tmp/qemu-test/src/include/qapi/error.h:166:25: error: format '%s' expects a matching 'char *' argument [-Werror=format=]
                         (fmt), ## __VA_ARGS__)
                         ^~~~~
/tmp/qemu-test/src/hw/misc/tmp421.c:166:9: note: in expansion of macro 'error_setg'
         error_setg(errp, "error reading %s: %s", errmsg);
         ^~~~~~~~~~
cc1: all warnings being treated as errors
make: *** [/tmp/qemu-test/src/rules.mak:69: hw/misc/tmp421.o] Error 1
make: *** Waiting for unfinished jobs....
Traceback (most recent call last):
  File "./tests/docker/docker.py", line 662, in <module>
---
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['sudo', '-n', 'docker', 'run', '--label', 'com.qemu.instance.uuid=aeeffa7799634227ba70e98b4d08d799', '-u', '1003', '--security-opt', 'seccomp=unconfined', '--rm', '-e', 'TARGET_LIST=', '-e', 'EXTRA_CONFIGURE_OPTS=', '-e', 'V=', '-e', 'J=14', '-e', 'DEBUG=', '-e', 'SHOW_ENV=', '-e', 'CCACHE_DIR=/var/tmp/ccache', '-v', '/home/patchew2/.cache/qemu-docker-ccache:/var/tmp/ccache:z', '-v', '/var/tmp/patchew-tester-tmp-4zudpx4q/src/docker-src.2019-10-18-19.29.53.18800:/var/tmp/qemu:z,ro', 'qemu:fedora', '/var/tmp/qemu/run', 'test-mingw']' returned non-zero exit status 2.
filter=--filter=label=com.qemu.instance.uuid=aeeffa7799634227ba70e98b4d08d799
make[1]: *** [docker-run] Error 1
make[1]: Leaving directory `/var/tmp/patchew-tester-tmp-4zudpx4q/src'
make: *** [docker-run-test-mingw@fedora] Error 2

real    2m1.697s
user    0m7.284s


The full log is available at
http://patchew.org/logs/20191018104438.6158-1-thuth@redhat.com/testing.docker-mingw@fedora/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com
diff mbox series

Patch

diff --git a/hw/misc/tmp421.c b/hw/misc/tmp421.c
index 9f044705fa..f23c46a40a 100644
--- a/hw/misc/tmp421.c
+++ b/hw/misc/tmp421.c
@@ -120,7 +120,9 @@  static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
     int tempid;
 
     if (sscanf(name, "temperature%d", &tempid) != 1) {
-        error_setg(errp, "error reading %s: %m", name);
+        const char *errmsg = g_strerror(errno);
+        error_setg(errp, "error reading %s: %s", name, errmsg);
+        g_free((gpointer)errmsg);
         return;
     }
 
@@ -160,7 +162,9 @@  static void tmp421_set_temperature(Object *obj, Visitor *v, const char *name,
     }
 
     if (sscanf(name, "temperature%d", &tempid) != 1) {
-        error_setg(errp, "error reading %s: %m", name);
+        const char *errmsg = g_strerror(errno);
+        error_setg(errp, "error reading %s: %s", errmsg);
+        g_free((gpointer)errmsg);
         return;
     }
 
diff --git a/util/main-loop.c b/util/main-loop.c
index e3eaa55866..e95d93fef3 100644
--- a/util/main-loop.c
+++ b/util/main-loop.c
@@ -61,7 +61,9 @@  static void sigfd_handler(void *opaque)
         }
 
         if (len != sizeof(info)) {
-            printf("read from sigfd returned %zd: %m\n", len);
+            const char *errmsg = g_strerror(errno);
+            error_report("read from sigfd returned %zd: %s", len, errmsg);
+            g_free((gpointer)errmsg);
             return;
         }
 
diff --git a/util/systemd.c b/util/systemd.c
index d22e86c707..17862c704f 100644
--- a/util/systemd.c
+++ b/util/systemd.c
@@ -59,9 +59,10 @@  unsigned int check_socket_activation(void)
              * descriptor is invalid, so socket activation has gone wrong
              * and we should exit.
              */
+            const char *errmsg = g_strerror(errno);
             error_report("Socket activation failed: "
-                         "invalid file descriptor fd = %d: %m",
-                         fd);
+                         "invalid file descriptor fd = %d: %s", fd, errmsg);
+            g_free((gpointer)errmsg);
             exit(EXIT_FAILURE);
         }
     }