[nginx] Disable symlinks: use O_PATH to open path components.

Valentin Bartenev vbart at nginx.com
Mon Sep 2 04:10:41 UTC 2013


details:   http://hg.nginx.org/nginx/rev/acd51b0f6fd4
branches:  
changeset: 5356:acd51b0f6fd4
user:      Valentin Bartenev <vbart at nginx.com>
date:      Mon Sep 02 08:07:59 2013 +0400
description:
Disable symlinks: use O_PATH to open path components.

It was introduced in Linux 2.6.39, glibc 2.14 and allows to obtain
file descriptors without actually opening files.  Thus made it possible
to traverse path with openat() syscalls without the need to have read
permissions for path components.  It is effectively emulates O_SEARCH
which is missing on Linux.

O_PATH is used in combination with O_RDONLY.  The last one is ignored
if O_PATH is used, but it allows nginx to not fail when it was built on
modern system (i.e. glibc 2.14+) and run with a kernel older than 2.6.39.
Then O_PATH is unknown to the kernel and ignored, while O_RDONLY is used.

Sadly, fstat() is not working with O_PATH descriptors till Linux 3.6.
As a workaround we fallback to fstatat() with the AT_EMPTY_PATH flag
that was introduced at the same time as O_PATH.

diffstat:

 auto/os/linux                  |  16 ++++++++++
 src/core/ngx_open_file_cache.c |  67 ++++++++++++++++++++++++++++++++++++++++++
 src/os/unix/ngx_files.h        |   3 +
 3 files changed, 86 insertions(+), 0 deletions(-)

diffs (136 lines):

diff -r 32847478c2c1 -r acd51b0f6fd4 auto/os/linux
--- a/auto/os/linux	Mon Sep 02 08:07:44 2013 +0400
+++ b/auto/os/linux	Mon Sep 02 08:07:59 2013 +0400
@@ -68,6 +68,22 @@ if [ $ngx_found = yes ]; then
 fi
 
 
+# O_PATH and AT_EMPTY_PATH were introduced in 2.6.39, glibc 2.14
+
+ngx_feature="O_PATH"
+ngx_feature_name="NGX_HAVE_O_PATH"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/types.h>
+                  #include <sys/stat.h>
+                  #include <fcntl.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="int fd; struct stat sb;
+                  fd = openat(AT_FDCWD, \".\", O_PATH|O_DIRECTORY|O_NOFOLLOW);
+                  if (fstatat(fd, \"\", &sb, AT_EMPTY_PATH) != 0) return 1"
+. auto/feature
+
+
 # sendfile()
 
 CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE"
diff -r 32847478c2c1 -r acd51b0f6fd4 src/core/ngx_open_file_cache.c
--- a/src/core/ngx_open_file_cache.c	Mon Sep 02 08:07:44 2013 +0400
+++ b/src/core/ngx_open_file_cache.c	Mon Sep 02 08:07:59 2013 +0400
@@ -25,6 +25,10 @@ static void ngx_open_file_cache_cleanup(
 #if (NGX_HAVE_OPENAT)
 static ngx_fd_t ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
     ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log);
+#if (NGX_HAVE_O_PATH)
+static ngx_int_t ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi,
+    ngx_log_t *log);
+#endif
 #endif
 static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name,
     ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create,
@@ -517,10 +521,17 @@ ngx_openat_file_owner(ngx_fd_t at_fd, co
         goto failed;
     }
 
+#if (NGX_HAVE_O_PATH)
+    if (ngx_file_o_path_info(fd, &fi, log) == NGX_ERROR) {
+        err = ngx_errno;
+        goto failed;
+    }
+#else
     if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
         err = ngx_errno;
         goto failed;
     }
+#endif
 
     if (fi.st_uid != atfi.st_uid) {
         err = NGX_ELOOP;
@@ -541,8 +552,64 @@ failed:
     return NGX_INVALID_FILE;
 }
 
+
+#if (NGX_HAVE_O_PATH)
+
+static ngx_int_t
+ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi, ngx_log_t *log)
+{
+    static ngx_uint_t  use_fstat = 1;
+
+    /*
+     * In Linux 2.6.39 the O_PATH flag was introduced that allows to obtain
+     * a descriptor without actually opening file or directory.  It requires
+     * less permissions for path components, but till Linux 3.6 fstat() returns
+     * EBADF on such descriptors, and fstatat() with the AT_EMPTY_PATH flag
+     * should be used instead.
+     *
+     * Three scenarios are handled in this function:
+     *
+     * 1) The kernel is newer than 3.6 or fstat() with O_PATH support was
+     *    backported by vendor.  Then fstat() is used.
+     *
+     * 2) The kernel is newer than 2.6.39 but older than 3.6.  In this case
+     *    the first call of fstat() returns EBADF and we fallback to fstatat()
+     *    with AT_EMPTY_PATH which was introduced at the same time as O_PATH.
+     *
+     * 3) The kernel is older than 2.6.39 but nginx was build with O_PATH
+     *    support.  Since descriptors are opened with O_PATH|O_RDONLY flags
+     *    and O_PATH is ignored by the kernel then the O_RDONLY flag is
+     *    actually used.  In this case fstat() just works.
+     */
+
+    if (use_fstat) {
+        if (ngx_fd_info(fd, fi) != NGX_FILE_ERROR) {
+            return NGX_OK;
+        }
+
+        if (ngx_errno != NGX_EBADF) {
+            return NGX_ERROR;
+        }
+
+        ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                      "fstat(O_PATH) failed with EBADF, "
+                      "switching to fstatat(AT_EMPTY_PATH)");
+
+        use_fstat = 0;
+        return ngx_file_o_path_info(fd, fi, log);
+    }
+
+    if (ngx_file_at_info(fd, "", fi, AT_EMPTY_PATH) != NGX_FILE_ERROR) {
+        return NGX_OK;
+    }
+
+    return NGX_ERROR;
+}
+
 #endif
 
+#endif /* NGX_HAVE_OPENAT */
+
 
 static ngx_fd_t
 ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
diff -r 32847478c2c1 -r acd51b0f6fd4 src/os/unix/ngx_files.h
--- a/src/os/unix/ngx_files.h	Mon Sep 02 08:07:44 2013 +0400
+++ b/src/os/unix/ngx_files.h	Mon Sep 02 08:07:59 2013 +0400
@@ -91,6 +91,9 @@ typedef struct {
 #elif defined(O_EXEC)
 #define NGX_FILE_SEARCH          (O_EXEC|NGX_FILE_DIRECTORY)
 
+#elif (NGX_HAVE_O_PATH)
+#define NGX_FILE_SEARCH          (O_PATH|O_RDONLY|NGX_FILE_DIRECTORY)
+
 #else
 #define NGX_FILE_SEARCH          (O_RDONLY|NGX_FILE_DIRECTORY)
 #endif



More information about the nginx-devel mailing list