diff --git a/tests/bwrap-lock.c b/tests/bwrap-lock.c
index 6fe95c39f9dd40d6e820e61f93dc95f41bd5f29c..88b8fed161fa3e2c13a37e6601d5aa438dbe1c3e 100644
--- a/tests/bwrap-lock.c
+++ b/tests/bwrap-lock.c
@@ -37,7 +37,7 @@
 
 typedef struct
 {
-  int unused;
+  TestsOpenFdSet old_fds;
 } Fixture;
 
 typedef struct
@@ -50,6 +50,8 @@ setup (Fixture *f,
        gconstpointer context)
 {
   G_GNUC_UNUSED const Config *config = context;
+
+  f->old_fds = tests_check_fd_leaks_enter ();
 }
 
 static void
@@ -57,6 +59,8 @@ teardown (Fixture *f,
           gconstpointer context)
 {
   G_GNUC_UNUSED const Config *config = context;
+
+  tests_check_fd_leaks_leave (f->old_fds);
 }
 
 static void
diff --git a/tests/meson.build b/tests/meson.build
index 164588408b46b2f49d19b33490c301cb96e0e20b..78d4b047120f854db82d71625a1db3dd99c43c1e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -43,7 +43,7 @@ compiled_tests = [
 foreach test_name : compiled_tests
   exe = executable(
     'test-' + test_name,
-    files(test_name + '.c'),
+    files(test_name + '.c', 'test-utils.c'),
     dependencies : [gio_unix, libglnx.get_variable('libglnx_dep')],
     link_with : [pressure_vessel_utils],
     include_directories : project_include_dirs,
diff --git a/tests/test-utils.c b/tests/test-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..fde49e1e88cf1615339ba34d5002148032a633b3
--- /dev/null
+++ b/tests/test-utils.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright © 2019-2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test-utils.h"
+
+#include <glib.h>
+#include "libglnx/libglnx.h"
+
+TestsOpenFdSet
+tests_check_fd_leaks_enter (void)
+{
+  g_autoptr(GHashTable) ret = NULL;
+  g_auto(GLnxDirFdIterator) iter = { FALSE };
+  g_autoptr(GError) error = NULL;
+
+  ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  glnx_dirfd_iterator_init_at (AT_FDCWD, "/proc/self/fd", TRUE,
+                               &iter, &error);
+  g_assert_no_error (error);
+
+  while (TRUE)
+    {
+      g_autofree gchar *target = NULL;
+      struct dirent *dent;
+      gint64 which;
+      char *endptr;
+
+      glnx_dirfd_iterator_next_dent (&iter, &dent, NULL, &error);
+      g_assert_no_error (error);
+
+      if (dent == NULL)
+        break;
+
+      if (g_str_equal (dent->d_name, ".") || g_str_equal (dent->d_name, ".."))
+        continue;
+
+      which = g_ascii_strtoll (dent->d_name, &endptr, 10);
+
+      if (endptr == NULL || *endptr != '\0')
+        {
+          g_warning ("Found unexpected entry \"%s\" in /proc/self/fd",
+                     dent->d_name);
+          continue;
+        }
+
+      if (which == (gint64) iter.fd)
+        continue;
+
+      /* ignore error, just let it be NULL */
+      target = glnx_readlinkat_malloc (iter.fd, dent->d_name, NULL, NULL);
+      g_hash_table_replace (ret, g_strdup (dent->d_name),
+                            g_steal_pointer (&target));
+    }
+
+  return g_steal_pointer (&ret);
+}
+
+void
+tests_check_fd_leaks_leave (TestsOpenFdSet fds)
+{
+  g_auto(GLnxDirFdIterator) iter = { FALSE };
+  g_autoptr(GError) error = NULL;
+
+  glnx_dirfd_iterator_init_at (AT_FDCWD, "/proc/self/fd", TRUE,
+                               &iter, &error);
+  g_assert_no_error (error);
+
+  while (TRUE)
+    {
+      g_autofree gchar *target = NULL;
+      gpointer val = NULL;
+      gint64 which;
+      struct dirent *dent;
+      char *endptr;
+
+      glnx_dirfd_iterator_next_dent (&iter, &dent, NULL, &error);
+      g_assert_no_error (error);
+
+      if (dent == NULL)
+        break;
+
+      if (g_str_equal (dent->d_name, ".") || g_str_equal (dent->d_name, ".."))
+        continue;
+
+      which = g_ascii_strtoll (dent->d_name, &endptr, 10);
+
+      if (endptr == NULL || *endptr != '\0')
+        {
+          g_warning ("Found unexpected entry \"%s\" in /proc/self/fd",
+                     dent->d_name);
+          continue;
+        }
+
+      if (which == (gint64) iter.fd)
+        continue;
+
+      /* ignore error, just let it be NULL */
+      target = glnx_readlinkat_malloc (iter.fd, dent->d_name, NULL, NULL);
+
+      if (g_hash_table_lookup_extended (fds, dent->d_name, NULL, &val))
+        {
+          g_assert_cmpstr (target, ==, val);
+        }
+      else
+        {
+          g_error ("fd %s \"%s\" was leaked",
+                   dent->d_name, target == NULL ? "(null)" : target);
+        }
+    }
+
+  g_hash_table_unref (fds);
+}
diff --git a/tests/test-utils.h b/tests/test-utils.h
index 5e0f774c4dad955a309eca3bbba9a1bcd27b2c3a..8d8ecc8c189fcce5cdecee3b66d592b12306d921 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019 Collabora Ltd.
+ * Copyright © 2019-2020 Collabora Ltd.
  *
  * SPDX-License-Identifier: MIT
  *
@@ -55,3 +55,11 @@
 #ifndef g_assert_cmpstr
 #define g_assert_cmpstr(a, op, b) g_assert (g_strcmp0 ((a), (b)) op 0)
 #endif
+
+/*
+ * Other assorted test helpers.
+ */
+
+typedef GHashTable *TestsOpenFdSet;
+TestsOpenFdSet tests_check_fd_leaks_enter (void);
+void tests_check_fd_leaks_leave (TestsOpenFdSet fds);