[gnome-multi-writer] Add gnome-multi-writer-probe as a tool to check for fake USB drives



commit ca1a2e6e6b906f0d7888352d1f36d38995ad73d0
Author: Richard Hughes <richard hughsie com>
Date:   Wed Jan 28 13:28:24 2015 +0000

    Add gnome-multi-writer-probe as a tool to check for fake USB drives

 configure.ac                       |    1 +
 contrib/gnome-multi-writer.spec.in |    1 +
 po/POTFILES.in                     |    1 +
 src/Makefile.am                    |   13 +
 src/gmw-probe.c                    |  527 ++++++++++++++++++++++++++++++++++++
 5 files changed, 543 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 98ae409..3bdd877 100644
--- a/configure.ac
+++ b/configure.ac
@@ -90,6 +90,7 @@ dnl ---------------------------------------------------------------------------
 PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.31.10 gobject-2.0 gmodule-2.0 gio-2.0 >= 2.25.9)
 PKG_CHECK_MODULES(GUSB, gusb >= 0.2.2)
 PKG_CHECK_MODULES(UDISKS, udisks2)
+PKG_CHECK_MODULES(GUDEV, gudev-1.0)
 PKG_CHECK_MODULES(GTK, gtk+-3.0 >= 3.11.2)
 PKG_CHECK_MODULES(CANBERRA, libcanberra-gtk3 >= 0.10)
 AC_PATH_PROG(APPSTREAM_UTIL, [appstream-util], [unfound])
diff --git a/contrib/gnome-multi-writer.spec.in b/contrib/gnome-multi-writer.spec.in
index 4fb80d2..4c71255 100644
--- a/contrib/gnome-multi-writer.spec.in
+++ b/contrib/gnome-multi-writer.spec.in
@@ -18,6 +18,7 @@ BuildRequires: itstool
 BuildRequires: libcanberra-devel >= 0.10
 BuildRequires: libgusb-devel >= 0.2.4
 BuildRequires: libudisks2-devel
+BuildRequires: libgudev1-devel
 
 Requires:      gnome-icon-theme-extras
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 05fa7b5..1fc0a25 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,3 +6,4 @@ data/org.gnome.MultiWriter.gschema.xml
 data/org.gnome.MultiWriter.desktop.in
 src/gmw-device.c
 src/gmw-main.c
+src/gmw-probe.c
diff --git a/src/Makefile.am b/src/Makefile.am
index d209904..eb185bf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,6 +2,7 @@ AM_CPPFLAGS =                                           \
        $(CANBERRA_CFLAGS)                              \
        $(GLIB_CFLAGS)                                  \
        $(GTK_CFLAGS)                                   \
+       $(GUDEV_CFLAGS)                                 \
        $(GUSB_CFLAGS)                                  \
        $(SOUP_CFLAGS)                                  \
        $(UDISKS_CFLAGS)                                \
@@ -10,6 +11,7 @@ AM_CPPFLAGS =                                         \
        -DLOCALEDIR=\""$(localedir)"\"
 
 bin_PROGRAMS =                                         \
+       gnome-multi-writer-probe                        \
        gnome-multi-writer
 
 gnome_multi_writer_LDADD =                             \
@@ -30,6 +32,17 @@ gnome_multi_writer_SOURCES =                         \
        gmw-resources.c                                 \
        gmw-resources.h
 
+gnome_multi_writer_probe_LDADD =                       \
+       $(GLIB_LIBS)                                    \
+       $(GUDEV_LIBS)
+
+gnome_multi_writer_probe_CFLAGS =                      \
+       $(WARNINGFLAGS_C)
+
+gnome_multi_writer_probe_SOURCES =                     \
+       gmw-cleanup.h                                   \
+       gmw-probe.c
+
 gmw-resources.c: gnome-multi-writer.gresource.xml ../data/gmw-main.ui ../data/gmw-menus.ui
        $(AM_V_GEN)                                     \
        glib-compile-resources --target=$@              \
diff --git a/src/gmw-probe.c b/src/gmw-probe.c
new file mode 100644
index 0000000..26cf5c6
--- /dev/null
+++ b/src/gmw-probe.c
@@ -0,0 +1,527 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gudev/gudev.h>
+#include <linux/fs.h>
+#include <linux/usbdevice_fs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "gmw-cleanup.h"
+
+#define ONE_MB         (1024 * 1024)
+#define ONE_BLOCK      (32 * 1024)
+
+typedef struct {
+       guint8          *data_old;
+       guint8          *data_random;
+       gssize           bytes_read;
+       gssize           bytes_wrote;
+       guint64          address;
+       gboolean         valid;
+} GmwProbeBlock;
+
+typedef struct {
+       guint64          disk_size;
+       int              fd;
+       gchar           *block_dev;
+       GPtrArray       *data_save;
+       GUdevDevice     *udev_device;
+} GmwProbeDevice;
+
+#define GMW_ERROR              1
+#define GMW_ERROR_FAILED       0
+#define GMW_ERROR_IS_FAKE      1
+
+/**
+ * gmw_probe_get_random_data:
+ **/
+static guint8 *
+gmw_probe_get_random_data (guint len)
+{
+       guint i;
+       guint8 *data;
+
+       data = g_new (guint8, len);
+       for (i = 0; i < len; i++)
+               data[i] = g_random_int_range ('a', 'z');
+       return data;
+}
+
+/**
+ * gmw_probe_block_free:
+ **/
+static void
+gmw_probe_block_free (GmwProbeBlock *item)
+{
+       g_free (item->data_old);
+       g_free (item->data_random);
+       g_free (item);
+}
+
+/**
+ * gmw_probe_device_reset:
+ **/
+static gboolean
+gmw_probe_device_reset (GmwProbeDevice *dev, GError **error)
+{
+       int fd;
+       const gchar *devnode;
+
+       /* just reset device */
+       devnode = g_udev_device_get_device_file (dev->udev_device);
+       g_debug ("resetting %s", devnode);
+       fd = g_open (devnode, O_WRONLY | O_NONBLOCK);
+       if (fd < 0) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "failed to open %s", devnode);
+               return FALSE;
+       }
+       if (ioctl (fd, USBDEVFS_RESET) != 0) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "Failed to reset device");
+               close (fd);
+               return FALSE;
+       }
+       close (fd);
+       return TRUE;
+}
+
+/**
+ * gmw_probe_device_open:
+ **/
+static gboolean
+gmw_probe_device_open (GmwProbeDevice *dev, GError **error)
+{
+       dev->fd = g_open (dev->block_dev, O_RDWR | O_SYNC);
+       if (dev->fd < 0) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "failed to open %s", dev->block_dev);
+               return FALSE;
+       }
+
+       /* do not use the OS cache */
+       posix_fadvise (dev->fd, 0, 0, POSIX_FADV_DONTNEED |
+                                     POSIX_FADV_RANDOM |
+                                     POSIX_FADV_NOREUSE);
+       return TRUE;
+}
+
+/**
+ * gmw_probe_device_free:
+ **/
+static void
+gmw_probe_device_free (GmwProbeDevice *dev)
+{
+       if (dev->fd > 0)
+               close (dev->fd);
+       g_free (dev->block_dev);
+       g_ptr_array_unref (dev->data_save);
+       g_free (dev);
+}
+
+/**
+ * gmw_probe_device_read:
+ **/
+static gsize
+gmw_probe_device_read (GmwProbeDevice *dev, guint64 addr, guint8 *buf, gssize len)
+{
+       gsize bytes_read;
+       lseek (dev->fd, addr, SEEK_SET);
+       bytes_read = read (dev->fd, buf, len);
+       g_debug ("read %" G_GSSIZE_FORMAT " @ %liMB", bytes_read, addr / ONE_MB);
+       return bytes_read;
+}
+
+/**
+ * gmw_probe_device_write:
+ **/
+static gsize
+gmw_probe_device_write (GmwProbeDevice *dev, guint64 addr, const guint8 *buf, gssize len)
+{
+       gsize bytes_written;
+       lseek (dev->fd, addr, SEEK_SET);
+       bytes_written = write (dev->fd, buf, len);
+       g_debug ("wrote %" G_GSSIZE_FORMAT " @ %liMB", bytes_written, addr / ONE_MB);
+       return bytes_written;
+}
+
+/**
+ * gmw_probe_device_data_save:
+ **/
+static gboolean
+gmw_probe_device_data_save (GmwProbeDevice *dev,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GmwProbeBlock *item;
+       guint i;
+
+       for (i = 1; i < 40; i++) {
+               item = g_new0 (GmwProbeBlock, 1);
+               item->valid = TRUE;
+               item->address = i * ONE_MB * 32;
+               item->data_old = g_new0 (guint8, ONE_BLOCK);
+               if (item->address >= dev->disk_size) {
+                       gmw_probe_block_free (item);
+                       break;
+               }
+               item->data_random = gmw_probe_get_random_data (ONE_BLOCK);
+               item->bytes_read = gmw_probe_device_read (dev,
+                                                         item->address,
+                                                         item->data_old,
+                                                         ONE_BLOCK);
+               g_ptr_array_add (dev->data_save, item);
+               if (item->bytes_read != ONE_BLOCK)
+                       break;
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gmw_probe_device_data_set_dummy:
+ **/
+static gboolean
+gmw_probe_device_data_set_dummy (GmwProbeDevice *dev,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       GmwProbeBlock *item;
+       guint i;
+
+       for (i = 0; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+               item->bytes_wrote = gmw_probe_device_write (dev,
+                                                           item->address,
+                                                           item->data_random,
+                                                           ONE_BLOCK);
+
+               if (item->bytes_wrote != ONE_BLOCK)
+                       break;
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gmw_probe_device_data_verify:
+ **/
+static gboolean
+gmw_probe_device_data_verify (GmwProbeDevice *dev,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GmwProbeBlock *item;
+       guint i;
+       guint32 offset;
+       _cleanup_free_ guint8 *wbuf2 = NULL;
+
+       wbuf2 = g_new (guint8, ONE_BLOCK + 0xff);
+       for (i = 0; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+
+               /* use a random offset to confuse drives that are just saving
+                * the address and data in some phantom FAT */
+               offset = g_random_int_range (1, 0xff);
+               item->bytes_read = gmw_probe_device_read (dev,
+                                                         item->address - offset,
+                                                         wbuf2,
+                                                         ONE_BLOCK + offset);
+               if (item->bytes_read != ONE_BLOCK + offset) {
+                       g_set_error (error,
+                                    GMW_ERROR,
+                                    GMW_ERROR_FAILED,
+                                    "failed to read data");
+                       return FALSE;
+               }
+               item->valid = memcmp (item->data_random,
+                                     wbuf2 + offset,
+                                     ONE_BLOCK) == 0;
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       return FALSE;
+
+               /* optimize; we don't need to check any more */
+               if (!item->valid)
+                       break;
+       }
+
+       /* if we aborted early, the rest of the drive is junk */
+       for (i = i; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+               item->valid = FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gmw_probe_device_data_restore:
+ **/
+static gboolean
+gmw_probe_device_data_restore (GmwProbeDevice *dev,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       GmwProbeBlock *item;
+       guint i;
+
+       for (i = 0; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+               if (!item->valid)
+                       continue;
+               item->bytes_wrote = gmw_probe_device_write (dev,
+                                                           item->address,
+                                                           item->data_old,
+                                                           ONE_BLOCK);
+               if (item->bytes_wrote != ONE_BLOCK)
+                       break;
+               if (g_cancellable_set_error_if_cancelled (cancellable, error))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gmw_probe_scan_device:
+ **/
+static gboolean
+gmw_probe_scan_device (GmwProbeDevice *dev, GCancellable *cancellable, GError **error)
+{
+       GmwProbeBlock *item;
+       guint i;
+
+       /* open block device */
+       if (!gmw_probe_device_open (dev, error))
+               return FALSE;
+
+       /* get reported size */
+       if (ioctl (dev->fd, BLKGETSIZE64, &dev->disk_size) != 0) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "Failed to get reported size");
+               return FALSE;
+       }
+       if (dev->disk_size == 0) {
+               g_set_error_literal (error,
+                                    GMW_ERROR,
+                                    GMW_ERROR_FAILED,
+                                    "disk capacity zero");
+               return FALSE;
+       }
+       if (dev->disk_size > 0x800000000llu) {
+               g_set_error_literal (error,
+                                    GMW_ERROR,
+                                    GMW_ERROR_FAILED,
+                                    "disk capacity invalid");
+               return FALSE;
+       }
+       g_debug ("disk reports to be %luMB in size", dev->disk_size / ONE_MB);
+
+       /* save data that's there already */
+       if (!gmw_probe_device_data_save (dev, cancellable, error))
+               return FALSE;
+
+       /* write 32k or random to every 32Mb */
+       if (!gmw_probe_device_data_set_dummy (dev, cancellable, error)) {
+               gmw_probe_device_data_restore (dev, cancellable, NULL);
+               return FALSE;
+       }
+
+       /* sanity check for really broken devices */
+       for (i = 0; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+               if (item->bytes_read != item->bytes_wrote) {
+                       g_set_error (error,
+                                    GMW_ERROR,
+                                    GMW_ERROR_FAILED,
+                                    "Failed to write len at %liMB",
+                                    item->address / ONE_MB);
+                       gmw_probe_device_data_restore (dev, cancellable, NULL);
+                       return FALSE;
+               }
+       }
+
+       /* reset device */
+       if (!gmw_probe_device_reset (dev, error)) {
+               gmw_probe_device_data_restore (dev, cancellable, NULL);
+               return FALSE;
+       }
+
+       /* wait for block drive to reappear */
+       close (dev->fd);
+       if (!gmw_probe_device_open (dev, error))
+               return FALSE;
+
+       /* read each chunk in again */
+       if (!gmw_probe_device_data_verify (dev, cancellable, error)) {
+               gmw_probe_device_data_restore (dev, cancellable, NULL);
+               return FALSE;
+       }
+
+       /* write back original data */
+       if (!gmw_probe_device_data_restore (dev, cancellable, error))
+               return FALSE;
+
+       /* get results */
+       for (i = 0; i < dev->data_save->len; i++) {
+               item = g_ptr_array_index (dev->data_save, i);
+               if (!item->valid) {
+                       g_set_error (error,
+                                    GMW_ERROR,
+                                    GMW_ERROR_IS_FAKE,
+                                    "Failed to verify data at %liMB",
+                                    item->address / ONE_MB);
+                       return FALSE;
+               }
+       }
+       return TRUE;
+}
+
+/**
+ * gmw_probe_use_device:
+ **/
+static gboolean
+gmw_probe_use_device (GUdevClient *udev_client,
+                     const gchar *block_dev,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       _cleanup_object_unref_ GUdevDevice *udev_device = NULL;
+       GmwProbeDevice *dev;
+
+       /* create worker object */
+       dev = g_new0 (GmwProbeDevice, 1);
+       dev->block_dev = g_strdup (block_dev);
+       dev->data_save = g_ptr_array_new_with_free_func ((GDestroyNotify) gmw_probe_block_free);
+
+       /* find udev device */
+       udev_device = g_udev_client_query_by_device_file (udev_client, block_dev);
+       if (udev_device == NULL) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "failed to find %s", block_dev);
+               gmw_probe_device_free (dev);
+               return FALSE;
+       }
+       dev->udev_device = g_udev_device_get_parent_with_subsystem (udev_device,
+                                                                   "usb",
+                                                                   "usb_device");
+       if (dev->udev_device == NULL) {
+               g_set_error (error,
+                            GMW_ERROR,
+                            GMW_ERROR_FAILED,
+                            "failed to find %s usb_device", block_dev);
+               gmw_probe_device_free (dev);
+               return FALSE;
+       }
+
+       /* actually do the scanning now */
+       if (!gmw_probe_scan_device (dev, cancellable, error)) {
+               gmw_probe_device_free (dev);
+               return FALSE;
+       }
+
+       /* success */
+       gmw_probe_device_free (dev);
+       return TRUE;
+}
+
+/**
+ * main:
+ **/
+int
+main (int argc, char **argv)
+{
+       GOptionContext *context;
+       const gchar *subsystems[] = { "usb", NULL };
+       gboolean verbose;
+       int status = EXIT_SUCCESS;
+       _cleanup_object_unref_ GUdevClient *udev_client = NULL;
+       _cleanup_error_free_ GError *error = NULL;
+       _cleanup_object_unref_ GCancellable *cancellable = NULL;
+
+       const GOptionEntry options[] = {
+               { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
+                       /* TRANSLATORS: command line option */
+                       _("Show extra debugging information"), NULL },
+               { NULL}
+       };
+
+       g_random_set_seed (0xdead);
+       cancellable = g_cancellable_new ();
+       udev_client = g_udev_client_new (subsystems);
+
+       /* TRANSLATORS: A program to copy the LiveUSB image onto USB hardware */
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, options, NULL);
+       if (!g_option_context_parse (context, &argc, &argv, &error)) {
+               status = EXIT_FAILURE;
+               g_print ("Failed to parse command line: %s\n", error->message);
+               goto out;
+       }
+
+       if (verbose)
+               g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+       if (argc == 1) {
+               status = EXIT_FAILURE;
+               g_print ("Block device required as argument\n");
+               goto out;
+       }
+
+       /* probe device */
+       if (!gmw_probe_use_device (udev_client, argv[1], cancellable, &error)) {
+               status = EXIT_FAILURE;
+               if (g_error_matches (error, GMW_ERROR, GMW_ERROR_IS_FAKE)) {
+                       g_print ("Device is FAKE: %s\n", error->message);
+               } else {
+                       g_print ("Failed to scan device: %s\n", error->message);
+               }
+               goto out;
+       }
+       g_print ("Device is GOOD\n");
+out:
+       g_option_context_free (context);
+       return status;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]