[gnu.utils.bug] ``diff -'' uses uninitialized storage

eggert@twinsun.com (Paul Eggert) (08/29/89)

When given the ``-'' option, GNU diff 1.7 uses uninitialized storage, and
sometimes wrongly acts as though standard input is empty.  Here is a scenario
that illustrates the (machine-dependent) problem.

	% diff -c /etc/motd -
	*** /etc/motd	Mon Aug 14 22:23:16 1989
	--- Standard Input	Wed Dec 31 16:00:00 1969
	***************
	*** 1,1 ****
	- SunOS Release 4.0.3 (SDST_50) #1: Mon Aug 14 22:18:35 PDT 1989
	--- 0 ----
	% 

Here are the system calls that GNU diff executes, as reported by trace(1):

	 getpagesize () = 8192
	 brk (0x22d78) = 0
	 brk (0x24d7c) = 0
	 stat ("/etc/motd", 0xefffb28) = 0
	 open ("/etc/motd", 0, 0) = 3
	 read (3, "SunOS Release 4.0.3 (SDST_50) #1".., 63) = 63
	 read (0, "", 0) = 0
	 open ("/usr/share/lib/zoneinfo/localtim".., 0, 01) = 4
	 read (4, "".., 8192) = 754
	 close (4) = 0
	 ioctl (1, 0x40125401, 0xeffed78) = -1 ENODEV (No such device)
	 fstat (1, 0xeffeda8) = 0
	 brk (0x28d7c) = 0
	 close (3) = 0
	 write (1, "*** /etc/motd\tMon Aug 14 22:23:1".., 188) = 188
	 close (0) = 0
	 close (1) = 0
	 close (2) = 0
	 exit (1) = ?

The ``read (0, "", 0)'' is a symptom of the bug.  I traced it to the following
code in io.c's slurp():

	...
	  else if ((current->stat.st_mode & S_IFMT) == S_IFREG)
	    {
	      current->bufsize = current->stat.st_size;
	      current->buffer = (char *) xmalloc (current->bufsize + 2);
	      current->buffered_chars
		= read (current->desc, current->buffer, current->bufsize);
	      if (current->buffered_chars < 0)
		pfatal_with_name (current->name);
	    }
	  else
	    {
	      int cc;

	      current->bufsize = 4096;
	      current->buffer = (char *) xmalloc (current->bufsize + 2);
	      current->buffered_chars = 0;

	      /* Not a regular file; read it in a little at a time, growing the
		 buffer as necessary. */

When slurp() is applied to the standard input, current->stat.st_mode is
garbage; the if-test may succeed, and because st_size is zero, read() is asked
to read zero bytes (which it cheerfully does).  The problem stems from the
following code in diff.c's compare_files(), which does not initialize
stat.st_mode if inf[i].name is "-".

      if (inf[i].desc != -1
	  && strcmp (inf[i].name, "-"))
	{
	  char *filename = inf[i].name;

	  stat_result[i] = stat (filename, &inf[i].stat);
      ...

Here is a patch.  GNU diff refers to st_dev, st_ino, st_mode, st_mtime, and
st_size; instead of keeping a list of the referred-to members, it seems easier
to just zero the whole structure.

	*** old/diff.c	Aug 28 10:29:07 1989
	--- new/diff.c	Mon Aug 28 09:57:16 1989
	***************
	*** 384,391 ****

	    for (i = 0; i <= 1; i++)
	      {
	!       inf[i].stat.st_size = 0;
	!       inf[i].stat.st_mtime = 0;
		inf[i].dir_p = 0;
		stat_result[i] = 0;

	--- 384,390 ----

	    for (i = 0; i <= 1; i++)
	      {
	!       bzero(&inf[i].stat, sizeof(struct stat));
		inf[i].dir_p = 0;
		stat_result[i] = 0;