[PATCH] port-serial: don't close tty file descriptor twice

Brian, Sam Samuel.Brian at digi.com
Tue Jun 19 22:25:01 UTC 2018


After the tty is opened, the fd is passed off to a g_io_channel which
manages it. When the serial port is closed with _close_internal(),
g_io_channel_shutdown() is called which then calls g_io_unix_close() which
in turn close()s the fd.
Currently, _close_internal() performs a second explicit close() after the
g_io_channel shutdown which *in the best case scenario* will fail with EBADF.
This is a race condition that can result in other file descriptors being
closed.

This change avoids double closing the serial port fd.
---

Hey,
I had this problem with a Telit LE910-EU1. What do you think of the fix?

To give some more detail about the problem...

The g_io_channel_shutdown() calling close() can be verified with
this short program:

  $ cat test.c
  #include <glib.h>
  #include <errno.h>
  #include <stdio.h>
  #include <unistd.h>

  int main (int argc, char* argv[])
  {
    int ret;
    GIOChannel *channel;

    channel = g_io_channel_unix_new(STDIN_FILENO);
    g_io_channel_shutdown(channel, FALSE, NULL);

    ret = close(STDIN_FILENO);
    printf("close() -> %d: %m\n", ret);
    return 0;
  }

  $ gcc -o test test.c -g $(pkg-config --cflags --libs gio-2.0)
  $ ./test
  close() -> -1: Bad file descriptor

Here is a snippet of strace from ModemManager that demonstrates when the
race causes another fd to be closed:

  ### Close the tty (from g_io_channel_shutdown)
  [pid   413] close(9 <unfinished ...>
  [pid   687] <... poll resumed> )        = 1 ([{fd=7, revents=POLLIN}])
  ...
  ### tty's closed fd is reused by eventfd2
  [pid   687] SYS_356(0, 0x80800, 0xc6c4c8, 0, 0xc6c4d0) = 9
  ...
  [pid   687] poll([{fd=7, events=POLLIN}, {fd=6, events=POLLIN}, {fd=9, events=POLLIN}], 3, -1 <unfinished ...>
  ### First close succeeds (tty fd)
  [pid   413] <... close resumed> )       = 0
  ### Second (explicit) close succeeds (eventfd fd, oops)
  [pid   413] close(9)                    = 0

PID 413 is ModemManager, PID 687 is a clone process created with the flag
CLONE_FILES so the fd table is shared between parent and clone.
>From the clone's read/writes, it appears to be used to communicate with
dbus.

Strangely, closing the eventfd didn't always cause a noticable failure.
It was only when *somehow* the port-serial code tried to use an eventfd as
a tty and AT commands fail.

 src/mm-port-serial.c | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c
index 44699a76..9ba4e5d2 100644
--- a/src/mm-port-serial.c
+++ b/src/mm-port-serial.c
@@ -1262,6 +1262,12 @@ success:
 error:
     mm_warn ("(%s) failed to open serial device", device);
 
+    if (self->priv->fd >= 0) {
+        if (!self->priv->iochannel)
+            close (self->priv->fd);
+        self->priv->fd = -1;
+    }
+
     if (self->priv->iochannel) {
         g_io_channel_shutdown (self->priv->iochannel, FALSE, NULL);
         g_io_channel_unref (self->priv->iochannel);
@@ -1274,11 +1280,6 @@ error:
         self->priv->socket = NULL;
     }
 
-    if (self->priv->fd >= 0) {
-        close (self->priv->fd);
-        self->priv->fd = -1;
-    }
-
     return FALSE;
 }
 
@@ -1348,6 +1349,13 @@ _close_internal (MMPortSerial *self, gboolean force)
             tcflush (self->priv->fd, TCIOFLUSH);
         }
 
+        /* Close fd if not part of the iochannel (shutdown closes fd) */
+        if (self->priv->fd >= 0) {
+            if (!self->priv->iochannel)
+                close (self->priv->fd);
+            self->priv->fd = -1;
+        }
+
         /* Destroy channel */
         if (self->priv->iochannel) {
             data_watch_enable (self, FALSE);
@@ -1356,12 +1364,6 @@ _close_internal (MMPortSerial *self, gboolean force)
             self->priv->iochannel = NULL;
         }
 
-        /* Close fd, if any */
-        if (self->priv->fd >= 0) {
-            close (self->priv->fd);
-            self->priv->fd = -1;
-        }
-
         /* Destroy socket */
         if (self->priv->socket) {
             data_watch_enable (self, FALSE);
-- 
2.17.1


More information about the ModemManager-devel mailing list