This fixes a possible symlink attack in localcharset module. The security hole was probably small, but that's not a reason for not making it even smaller.
2009-10-18 Bruno Haible <br...@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 <fgl...@veracode.com>. *** lib/localcharset.c.orig 2009-10-18 16:53:18.000000000 +0200 --- lib/localcharset.c 2009-10-18 16:50:57.000000000 +0200 *************** *** 23,28 **** --- 23,29 ---- /* Specification. */ #include "localcharset.h" + #include <fcntl.h> #include <stddef.h> #include <stdio.h> #include <string.h> *************** *** 44,49 **** --- 45,51 ---- #endif #if !defined WIN32_NATIVE + # include <unistd.h> # if HAVE_LANGINFO_CODESET # include <langinfo.h> # else *************** *** 75,80 **** --- 77,87 ---- # 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,123 **** 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; --- 124,129 ---- *************** *** 143,219 **** } } ! if (file_name == NULL || (fp = fopen (file_name, "r")) == NULL) ! /* Out of memory or file not found, treat it as empty. */ cp = ""; else { ! /* 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; - } - } ! if (file_name != NULL) ! free (file_name); #else --- 149,253 ---- } } ! if (file_name == NULL) ! /* Out of memory. Treat the file as empty. */ cp = ""; else { ! int fd; ! /* 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 { ! FILE *fp; ! ! fp = fdopen (fd, "r"); ! if (fp == NULL) { ! /* Out of memory. Treat the file as empty. */ ! close (fd); ! cp = ""; } else { ! /* 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; ! } } } ! free (file_name); ! } #else *** m4/fcntl_h.m4.orig 2009-10-18 16:53:19.000000000 +0200 --- m4/fcntl_h.m4 2009-10-18 16:37:25.000000000 +0200 *************** *** 1,4 **** ! # serial 5 # Configure fcntl.h. dnl Copyright (C) 2006, 2007, 2009 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation --- 1,4 ---- ! # 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,15 **** --- 10,26 ---- 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,86 **** 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], --- 88,93 ---- *** m4/localcharset.m4.orig 2009-10-18 16:53:19.000000000 +0200 --- m4/localcharset.m4 2009-10-18 16:37:43.000000000 +0200 *************** *** 1,4 **** ! # localcharset.m4 serial 6 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, --- 1,4 ---- ! # 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,13 **** --- 8,14 ---- [ 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. *** modules/localcharset.orig 2009-10-18 16:53:19.000000000 +0200 --- modules/localcharset 2009-10-18 16:37:07.000000000 +0200 *************** *** 14,19 **** --- 14,20 ---- lib/ref-add.sin lib/ref-del.sin m4/codeset.m4 + m4/fcntl_h.m4 m4/glibc21.m4 m4/localcharset.m4