Avoid symlink attack in localcharset module. Use fcntl_h.m4 from gnulib.
diff --git a/libcharset/ChangeLog b/libcharset/ChangeLog
index 318f46f..7e41ad9 100644
--- a/libcharset/ChangeLog
+++ b/libcharset/ChangeLog
@@ -1,3 +1,8 @@
+2009-10-18 Bruno Haible <bruno@clisp.org>
+
+ * configure.ac: Invoke gl_FCNTL_O_FLAGS.
+ * INTEGRATE: Mention fcntl_h.m4 and the gl_FCNTL_O_FLAGS invocation.
+
2009-08-15 Bruno Haible <bruno@clisp.org>
* Makefile.devel (AUTOCONF, AUTOHEADER): Switch to version 2.64.
diff --git a/libcharset/INTEGRATE b/libcharset/INTEGRATE
index f4aa1de..2eb1b03 100644
--- a/libcharset/INTEGRATE
+++ b/libcharset/INTEGRATE
@@ -4,13 +4,14 @@
ref-del.sin) and the include file (include/localcharset.h) into your
package.
-* Add the m4/ files (codeset.m4, glibc21.m4) to your aclocal.m4 file or, if
- you are using automake, to your m4/ directory.
+* Add the m4/ files (codeset.m4, fcntl_h.m4, glibc21.m4) to your aclocal.m4
+ file or, if you are using automake, to your m4/ directory.
* Add the following lines to your configure.ac file:
AC_CANONICAL_HOST
AM_LANGINFO_CODESET
+ gl_FCNTL_O_FLAGS
jm_GLIBC21
AC_CHECK_HEADERS(stddef.h stdlib.h string.h)
diff --git a/libcharset/configure.ac b/libcharset/configure.ac
index 5e6a23f..8c29191 100644
--- a/libcharset/configure.ac
+++ b/libcharset/configure.ac
@@ -61,6 +61,7 @@
dnl checks for header files and functions
AM_LANGINFO_CODESET
+gl_FCNTL_O_FLAGS
gl_GLIBC21
AC_CHECK_FUNCS([setlocale])
diff --git a/libcharset/lib/ChangeLog b/libcharset/lib/ChangeLog
index f46fe98..375d23f 100644
--- a/libcharset/lib/ChangeLog
+++ b/libcharset/lib/ChangeLog
@@ -1,3 +1,11 @@
+2009-10-18 Bruno Haible <bruno@clisp.org>
+
+ Avoid symlink attack in localcharset module.
+ * 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.
+ Reported by Fergal Glynn <fglynn@veracode.com>.
+
2009-03-26 Bruno Haible <bruno@clisp.org>
* Makefile.in (install, installdir): Ensure that $(DESTDIR)$(libdir)
diff --git a/libcharset/lib/localcharset.c b/libcharset/lib/localcharset.c
index 434fc7c..e808967 100644
--- a/libcharset/lib/localcharset.c
+++ b/libcharset/lib/localcharset.c
@@ -24,6 +24,7 @@
/* Specification. */
#include "localcharset.h"
+#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
@@ -45,6 +46,7 @@
#endif
#if !defined WIN32_NATIVE
+# include <unistd.h>
# if HAVE_LANGINFO_CODESET
# include <langinfo.h>
# else
@@ -76,6 +78,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) == '\\')
@@ -118,7 +125,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;
@@ -144,77 +150,105 @@
}
}
- 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 (;;)
- {
- 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)
+ /* 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
{
- *(res_ptr + res_size) = '\0';
- cp = res_ptr;
- }
- }
+ FILE *fp;
- if (file_name != NULL)
- free (file_name);
+ 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
diff --git a/libcharset/m4/fcntl_h.m4 b/libcharset/m4/fcntl_h.m4
new file mode 100644
index 0000000..223fa48
--- /dev/null
+++ b/libcharset/m4/fcntl_h.m4
@@ -0,0 +1,108 @@
+# 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
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+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 <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],
+ [AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #ifndef O_NOATIME
+ #define O_NOATIME 0
+ #endif
+ #ifndef O_NOFOLLOW
+ #define O_NOFOLLOW 0
+ #endif
+ static int const constants[] =
+ {
+ O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND,
+ O_NONBLOCK, O_SYNC, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY
+ };
+ ]],
+ [[
+ int status = !constants;
+ {
+ static char const sym[] = "conftest.sym";
+ if (symlink (".", sym) != 0
+ || close (open (sym, O_RDONLY | O_NOFOLLOW)) == 0)
+ status |= 32;
+ unlink (sym);
+ }
+ {
+ static char const file[] = "confdefs.h";
+ int fd = open (file, O_RDONLY | O_NOATIME);
+ char c;
+ struct stat st0, st1;
+ if (fd < 0
+ || fstat (fd, &st0) != 0
+ || sleep (1) != 0
+ || read (fd, &c, 1) != 1
+ || close (fd) != 0
+ || stat (file, &st1) != 0
+ || st0.st_atime != st1.st_atime)
+ status |= 64;
+ }
+ return status;]])],
+ [gl_cv_header_working_fcntl_h=yes],
+ [case $? in #(
+ 32) gl_cv_header_working_fcntl_h='no (bad O_NOFOLLOW)';; #(
+ 64) gl_cv_header_working_fcntl_h='no (bad O_NOATIME)';; #(
+ 96) gl_cv_header_working_fcntl_h='no (bad O_NOATIME, O_NOFOLLOW)';; #(
+ *) gl_cv_header_working_fcntl_h='no';;
+ esac],
+ [gl_cv_header_working_fcntl_h=cross-compiling])])
+
+ case $gl_cv_header_working_fcntl_h in #(
+ *O_NOATIME* | no | cross-compiling) ac_val=0;; #(
+ *) ac_val=1;;
+ esac
+ AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOATIME], [$ac_val],
+ [Define to 1 if O_NOATIME works.])
+
+ case $gl_cv_header_working_fcntl_h in #(
+ *O_NOFOLLOW* | no | cross-compiling) ac_val=0;; #(
+ *) ac_val=1;;
+ esac
+ AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOFOLLOW], [$ac_val],
+ [Define to 1 if O_NOFOLLOW works.])
+])
+
+AC_DEFUN([gl_FCNTL_MODULE_INDICATOR],
+[
+ dnl Use AC_REQUIRE here, so that the default settings are expanded once only.
+ AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
+ GNULIB_[]m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./-],[ABCDEFGHIJKLMNOPQRSTUVWXYZ___])=1
+])
+
+AC_DEFUN([gl_FCNTL_H_DEFAULTS],
+[
+ GNULIB_OPEN=0; AC_SUBST([GNULIB_OPEN])
+ GNULIB_OPENAT=0; AC_SUBST([GNULIB_OPENAT])
+ dnl Assume proper GNU behavior unless another module says otherwise.
+ HAVE_OPENAT=1; AC_SUBST([HAVE_OPENAT])
+ REPLACE_OPEN=0; AC_SUBST([REPLACE_OPEN])
+ REPLACE_OPENAT=0; AC_SUBST([REPLACE_OPENAT])
+])