changeset 12180:2c71cf09ef7e

Avoid symlink attack in localcharset module.
author Bruno Haible <bruno@clisp.org>
date Sun, 18 Oct 2009 16:58:39 +0200
parents 70f69bf8b898
children 0e32a536d8eb
files ChangeLog lib/localcharset.c m4/fcntl_h.m4 m4/localcharset.m4 modules/localcharset
diffstat 5 files changed, 121 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2009-10-18  Bruno Haible  <bruno@clisp.org>
+
+	Avoid symlink attack in localcharset module.
+	* lib/localcharset.c: Include <fcntl.h>, <unistd.h>.
+	(O_NOFOLLOW): Define fallback.
+	(get_charset_aliases): Don't open the file if it is a symbolic link.
+	* m4/fcntl_h.m4 (gl_FCNTL_O_FLAGS): New macro, extracted from
+	gl_FCNTL_H.
+	(gl_FCNTL_H): Require it.
+	* m4/localcharset.m4 (gl_LOCALCHARSET): Likewise.
+	* modules/localcharset (Files): Add m4/fcntl_h.m4.
+	Reported by Fergal Glynn <fglynn@veracode.com>.
+
 2009-10-18  Bruno Haible  <bruno@clisp.org>
 
 	Implement nproc for mingw.
--- a/lib/localcharset.c
+++ b/lib/localcharset.c
@@ -23,6 +23,7 @@
 /* Specification.  */
 #include "localcharset.h"
 
+#include <fcntl.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
@@ -44,6 +45,7 @@
 #endif
 
 #if !defined WIN32_NATIVE
+# include <unistd.h>
 # if HAVE_LANGINFO_CODESET
 #  include <langinfo.h>
 # else
@@ -75,6 +77,11 @@
 # include "configmake.h"
 #endif
 
+/* Define O_NOFOLLOW to 0 on platforms where it does not exist.  */
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
   /* Win32, Cygwin, OS/2, DOS */
 # define ISSLASH(C) ((C) == '/' || (C) == '\\')
@@ -117,7 +124,6 @@
   if (cp == NULL)
     {
 #if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__)
-      FILE *fp;
       const char *dir;
       const char *base = "charset.alias";
       char *file_name;
@@ -143,78 +149,106 @@
 	  }
       }
 
-      if (file_name == NULL || (fp = fopen (file_name, "r")) == NULL)
-	/* Out of memory or file not found, treat it as empty.  */
+      if (file_name == NULL)
+	/* Out of memory.  Treat the file as empty.  */
 	cp = "";
       else
 	{
-	  /* Parse the file's contents.  */
-	  char *res_ptr = NULL;
-	  size_t res_size = 0;
+	  int fd;
 
-	  for (;;)
+	  /* Open the file.  Reject symbolic links on platforms that support
+	     O_NOFOLLOW.  This is a security feature.  Without it, an attacker
+	     could retrieve parts of the contents (namely, the tail of the
+	     first line that starts with "* ") of an arbitrary file by placing
+	     a symbolic link to that file under the name "charset.alias" in
+	     some writable directory and defining the environment variable
+	     CHARSETALIASDIR to point to that directory.  */
+	  fd = open (file_name,
+		     O_RDONLY | (HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0));
+	  if (fd < 0)
+	    /* File not found.  Treat it as empty.  */
+	    cp = "";
+	  else
 	    {
-	      int c;
-	      char buf1[50+1];
-	      char buf2[50+1];
-	      size_t l1, l2;
-	      char *old_res_ptr;
+	      FILE *fp;
 
-	      c = getc (fp);
-	      if (c == EOF)
-		break;
-	      if (c == '\n' || c == ' ' || c == '\t')
-		continue;
-	      if (c == '#')
+	      fp = fdopen (fd, "r");
+	      if (fp == NULL)
 		{
-		  /* Skip comment, to end of line.  */
-		  do
-		    c = getc (fp);
-		  while (!(c == EOF || c == '\n'));
-		  if (c == EOF)
-		    break;
-		  continue;
-		}
-	      ungetc (c, fp);
-	      if (fscanf (fp, "%50s %50s", buf1, buf2) < 2)
-		break;
-	      l1 = strlen (buf1);
-	      l2 = strlen (buf2);
-	      old_res_ptr = res_ptr;
-	      if (res_size == 0)
-		{
-		  res_size = l1 + 1 + l2 + 1;
-		  res_ptr = (char *) malloc (res_size + 1);
+		  /* Out of memory.  Treat the file as empty.  */
+		  close (fd);
+		  cp = "";
 		}
 	      else
 		{
-		  res_size += l1 + 1 + l2 + 1;
-		  res_ptr = (char *) realloc (res_ptr, res_size + 1);
-		}
-	      if (res_ptr == NULL)
-		{
-		  /* Out of memory. */
-		  res_size = 0;
-		  if (old_res_ptr != NULL)
-		    free (old_res_ptr);
-		  break;
+		  /* Parse the file's contents.  */
+		  char *res_ptr = NULL;
+		  size_t res_size = 0;
+
+		  for (;;)
+		    {
+		      int c;
+		      char buf1[50+1];
+		      char buf2[50+1];
+		      size_t l1, l2;
+		      char *old_res_ptr;
+
+		      c = getc (fp);
+		      if (c == EOF)
+			break;
+		      if (c == '\n' || c == ' ' || c == '\t')
+			continue;
+		      if (c == '#')
+			{
+			  /* Skip comment, to end of line.  */
+			  do
+			    c = getc (fp);
+			  while (!(c == EOF || c == '\n'));
+			  if (c == EOF)
+			    break;
+			  continue;
+			}
+		      ungetc (c, fp);
+		      if (fscanf (fp, "%50s %50s", buf1, buf2) < 2)
+			break;
+		      l1 = strlen (buf1);
+		      l2 = strlen (buf2);
+		      old_res_ptr = res_ptr;
+		      if (res_size == 0)
+			{
+			  res_size = l1 + 1 + l2 + 1;
+			  res_ptr = (char *) malloc (res_size + 1);
+			}
+		      else
+			{
+			  res_size += l1 + 1 + l2 + 1;
+			  res_ptr = (char *) realloc (res_ptr, res_size + 1);
+			}
+		      if (res_ptr == NULL)
+			{
+			  /* Out of memory. */
+			  res_size = 0;
+			  if (old_res_ptr != NULL)
+			    free (old_res_ptr);
+			  break;
+			}
+		      strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1);
+		      strcpy (res_ptr + res_size - (l2 + 1), buf2);
+		    }
+		  fclose (fp);
+		  if (res_size == 0)
+		    cp = "";
+		  else
+		    {
+		      *(res_ptr + res_size) = '\0';
+		      cp = res_ptr;
+		    }
 		}
-	      strcpy (res_ptr + res_size - (l2 + 1) - (l1 + 1), buf1);
-	      strcpy (res_ptr + res_size - (l2 + 1), buf2);
 	    }
-	  fclose (fp);
-	  if (res_size == 0)
-	    cp = "";
-	  else
-	    {
-	      *(res_ptr + res_size) = '\0';
-	      cp = res_ptr;
-	    }
+
+	  free (file_name);
 	}
 
-      if (file_name != NULL)
-	free (file_name);
-
 #else
 
 # if defined DARWIN7
--- a/m4/fcntl_h.m4
+++ b/m4/fcntl_h.m4
@@ -1,4 +1,4 @@
-# serial 5
+# serial 6
 # Configure fcntl.h.
 dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
@@ -10,6 +10,17 @@
 AC_DEFUN([gl_FCNTL_H],
 [
   AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+  AC_REQUIRE([gl_FCNTL_O_FLAGS])
+  gl_CHECK_NEXT_HEADERS([fcntl.h])
+  FCNTL_H='fcntl.h'
+  AC_SUBST([FCNTL_H])
+])
+
+# Test whether the flags O_NOATIME and O_NOFOLLOW actually work.
+# Define HAVE_WORKING_O_NOATIME to 1 if O_NOATIME works, or to 0 otherwise.
+# Define HAVE_WORKING_O_NOFOLLOW to 1 if O_NOFOLLOW works, or to 0 otherwise.
+AC_DEFUN([gl_FCNTL_O_FLAGS],
+[
   dnl Persuade glibc <fcntl.h> to define O_NOATIME and O_NOFOLLOW.
   AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
   AC_CACHE_CHECK([for working fcntl.h], [gl_cv_header_working_fcntl_h],
@@ -77,10 +88,6 @@
   esac
   AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOFOLLOW], [$ac_val],
     [Define to 1 if O_NOFOLLOW works.])
-
-  gl_CHECK_NEXT_HEADERS([fcntl.h])
-  FCNTL_H='fcntl.h'
-  AC_SUBST([FCNTL_H])
 ])
 
 AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],
--- a/m4/localcharset.m4
+++ b/m4/localcharset.m4
@@ -1,4 +1,4 @@
-# localcharset.m4 serial 6
+# localcharset.m4 serial 7
 dnl Copyright (C) 2002, 2004, 2006, 2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -8,6 +8,7 @@
 [
   dnl Prerequisites of lib/localcharset.c.
   AC_REQUIRE([AM_LANGINFO_CODESET])
+  AC_REQUIRE([gl_FCNTL_O_FLAGS])
   AC_CHECK_DECLS_ONCE([getc_unlocked])
 
   dnl Prerequisites of the lib/Makefile.am snippet.
--- a/modules/localcharset
+++ b/modules/localcharset
@@ -14,6 +14,7 @@
 lib/ref-add.sin
 lib/ref-del.sin
 m4/codeset.m4
+m4/fcntl_h.m4
 m4/glibc21.m4
 m4/localcharset.m4