From b06da86eb05ed57e2861061ae5cacf4c7a3686f1 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Sun, 18 Oct 2009 16:58:39 +0200 Subject: [PATCH] Avoid symlink attack in localcharset module. --- ChangeLog | 13 +++++ lib/localcharset.c | 156 +++++++++++++++++++++++++++++++-------------------- m4/fcntl_h.m4 | 17 ++++-- m4/localcharset.m4 | 3 +- modules/localcharset | 1 + 5 files changed, 123 insertions(+), 67 deletions(-) diff --git a/ChangeLog b/ChangeLog index fbd6694e7..afb77cd39 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ 2009-10-18 Bruno Haible + Avoid symlink attack in localcharset module. + * lib/localcharset.c: Include , . + (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 . + +2009-10-18 Bruno Haible + Implement nproc for mingw. * lib/nproc.c: Include (num_processors): On native Windows platforms, try GetSystemInfo. diff --git a/lib/localcharset.c b/lib/localcharset.c index 1ff46052d..ee43e81b3 100644 --- a/lib/localcharset.c +++ b/lib/localcharset.c @@ -23,6 +23,7 @@ /* Specification. */ #include "localcharset.h" +#include #include #include #include @@ -44,6 +45,7 @@ #endif #if !defined WIN32_NATIVE +# include # if HAVE_LANGINFO_CODESET # include # 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 @@ get_charset_aliases (void) 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,77 +149,105 @@ get_charset_aliases (void) } } - 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; - - for (;;) + 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 { - 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) + FILE *fp; + + fp = fdopen (fd, "r"); + if (fp == NULL) { - 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); + /* 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 (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); + free (file_name); + } #else diff --git a/m4/fcntl_h.m4 b/m4/fcntl_h.m4 index 118a4fa62..223fa4830 100644 --- 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 @@ dnl Written by Paul Eggert. 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 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 @@ AC_DEFUN([gl_FCNTL_H], 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], diff --git a/m4/localcharset.m4 b/m4/localcharset.m4 index e9601041c..90b9403d5 100644 --- 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 @@ AC_DEFUN([gl_LOCALCHARSET], [ 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. diff --git a/modules/localcharset b/modules/localcharset index 46020b0a0..02e9e4d68 100644 --- a/modules/localcharset +++ b/modules/localcharset @@ -14,6 +14,7 @@ lib/config.charset lib/ref-add.sin lib/ref-del.sin m4/codeset.m4 +m4/fcntl_h.m4 m4/glibc21.m4 m4/localcharset.m4 -- 2.11.0