[sane-devel] Segmentation faults in several backends

Henning Meier-Geinitz henning@meier-geinitz.de
Sat, 22 Feb 2003 18:01:31 +0100


--EVF5PPMfhYS0aIcm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

While trying to reproduce the problems with the net backend reported
some days ago I found some more backends that cause segfaults when
called more then once. By calling the backends more then once I mean:

sane_init
sane_get_device
[... other sane functions]
sane_exit
sane_init
sane_get_device
[... other sane functions]
sane_exit
[and so on]

Some backends don't initialize global variables in sane_init. If they
check for 0 afterwards, this test will be ok for the first run but not
for the second sane_init. So global variables must be initialized
explicitley in sane_init (or set to 0 in sane_exit). It's not enough
to just write
  int some_global_variable = 0;
because this initialization will be done only when the library was
loaded, not with every call of sane_init.

Usually variables like devlist, num_devices, first_device or
first_handle are the culprits. Please, everyone check if those are
initialized correctly. I guess that most backends don't initialize
them correctly.

With the usual setup you'll never notice the segfault because the
frontends call sane_init only once and never again. Further more, the
default is to link to libsane-dll and this backend unloads the library
in sane_exit. But there are reasons to link to backends directly so
these bugs should be fixed.

I'll attach a test program that calls the following sequence ten times:
sane_init
sane_get_devices
sane_open
sane_close
sane_exit

Compile with "gcc -o sane-test sane-test.c -ldl".

It loads every SANE library that exists in /usr/local/lib/sane
manually and will report errors and segmentation faults.

Please check with your backend. Some segfaults only occur when devices
are connected. Some backends segfault even without any devices:

testing libsane-artec.so: 0 1 2 got signal 11
testing libsane-mustek_pp.so: 0 1 got signal 11
testing libsane-pie.so: 0 1 2 got signal 11
testing libsane-umax.so: 0 1 2 got signal 11
testing libsane-umax_pp.so: 0 1 got signal 11

Bye,
  Henning

--EVF5PPMfhYS0aIcm
Content-Type: text/x-csrc; charset=us-ascii
Content-Disposition: attachment; filename="sane-test.c"

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

#include <sane/sane.h>

#define SANE_LIB_DIR "/usr/local/lib/sane/"

enum SANE_Ops
{
  OP_INIT = 0,
  OP_EXIT,
  OP_GET_DEVICES,
  OP_OPEN,
  OP_CLOSE,
  OP_STRSTATUS,
  NUM_OPS
};

static const char *op_name[] = {
  "sane_init", "sane_exit", "sane_get_devices", "sane_open", "sane_close", "sane_strstatus"
};

static void *(*op[NUM_OPS]) ();

static int got_signal = 0;

static void
sig_handler (int signal)
{
  fprintf (stderr, "got signal %d\n", signal);
  _exit (1);
}


int main ()
{
  struct stat stat_buf;
  DIR *dir;
  struct dirent *dir_entry;
  struct sigaction act;
  pid_t pid;

  if (stat (SANE_LIB_DIR, &stat_buf) < 0)
    {
      fprintf (stderr, "can't stat %s: %s\n", SANE_LIB_DIR, strerror (errno));
      return 1;
    }
  if (!S_ISDIR (stat_buf.st_mode))
    {
      fprintf (stderr, "%s is not a directory\n", SANE_LIB_DIR);
      return 1;
    }
  if ((dir = opendir (SANE_LIB_DIR)) == 0)
    {
      fprintf (stderr, "cannot read directory %s: %s\n", SANE_LIB_DIR, strerror (errno));
      return 1;
    }

  memset (&act, 0, sizeof (act));
  act.sa_handler = sig_handler;
  sigaction (SIGSEGV, &act, 0);
      
  while ((dir_entry = readdir (dir)) != 0)
    {
      char libpath[PATH_MAX];
      void *dl_handle;
      int i;
      SANE_Status status;
      SANE_Int version_code;
      const SANE_Device **device_list;

      got_signal = 0;
      if ((strlen (dir_entry->d_name) < strlen ("libsane.so")) 
	  || (strncmp ("libsane", dir_entry->d_name, strlen ("libsane")) != 0)
	  || (strncmp (dir_entry->d_name + strlen (dir_entry->d_name) - 3, ".so", 3) != 0))
	  continue;
      fprintf (stderr, "testing %s: ", dir_entry->d_name);
      sprintf (libpath, "%s%s", SANE_LIB_DIR, dir_entry->d_name);
      dl_handle = dlopen (libpath, RTLD_LAZY);
      if (!dl_handle)
	{
	  fprintf (stderr, "opening %s failed: %s\n", libpath, dlerror());
	  continue;
	}
      for (i = 0; i < NUM_OPS; i++)
	{
	  op[i] = (void *(*)(void)) dlsym (dl_handle, op_name[i]);
	  if (!op[i])
	    break;
	}
      if (i < NUM_OPS)
	{
	  fprintf (stderr, "getting symbol %s failed: %s\n", op_name[i], dlerror());
	  dlclose (dl_handle);
	  continue;
	}
      
      pid = fork ();

      if (pid == 0)
	{
	  for (i = 0; i < 10; i++)
	    {
	      SANE_Handle handle;

	      fprintf (stderr, "%d ", i);
	      status = (SANE_Status) (*op[OP_INIT]) (&version_code, 0);
	      if (status != SANE_STATUS_GOOD)
		{
		  fprintf (stderr, "sane_init failed (i = %d): %s\n", i, 
			   (SANE_String_Const) (*op[OP_STRSTATUS]) (status));
		  break;
		}
	      status = (SANE_Status) (*op[OP_GET_DEVICES]) (&device_list, SANE_FALSE);
	      if (status != SANE_STATUS_GOOD)
		{
		  fprintf (stderr, "sane_get_devices failed (i = %d): %s\n", i, 
			   (SANE_String_Const) (*op[OP_STRSTATUS]) (status));
		  break;
		}
	      status = (SANE_Status) (*op[OP_OPEN]) ("", &handle);
	      if (status == SANE_STATUS_GOOD)
		  (*op[OP_CLOSE]) (handle);

	      (*op[OP_EXIT]) ();
	    }
	  if (i < 10)
	    {
	      dlclose (dl_handle);
	      _exit (2);
	    }
	  _exit (0);
	}
      else if (pid > 0)
	{
	  int status;
	  waitpid (pid, &status, 0);
	  if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
	    fprintf (stderr, "ok\n");
	  dlclose (dl_handle);
	}
	      
    }

  fprintf (stderr, "done\n");
  return 0;
}

--EVF5PPMfhYS0aIcm--