From 4de0b65d6dbd5bc080972ad71073671f52c88ad2 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Aug 12 2014 01:05:46 +0000 Subject: Improve fixes for CVE-2014-2905 and CVE-2014-2914 This includes upstream work and my own followups. --- diff --git a/fish-upstream-CVE-2014-2905-part2.patch b/fish-upstream-CVE-2014-2905-part2.patch new file mode 100644 index 0000000..b1c444f --- /dev/null +++ b/fish-upstream-CVE-2014-2905-part2.patch @@ -0,0 +1,185 @@ +From b5cd21c337a8990c0c343ab2c22d3dc123a03d25 Mon Sep 17 00:00:00 2001 +Message-Id: +From: David Adam +Date: Mon, 4 Aug 2014 13:26:14 +0800 +Subject: [PATCH 1/3] Further fixes to universal variable server socket + management + +- Change fishd_path to std::string +- Warn, rather than exiting with an error, if the universal variable + server path is not available, and provide more useful advice. +- Export the new __fishd_runtime_dir variable. +--- + common.cpp | 13 +++++++------ + common.h | 2 +- + env.cpp | 4 ++-- + env_universal.cpp | 22 ++++++++++++++-------- + env_universal.h | 2 +- + fish_pager.cpp | 4 ++-- + fishd.cpp | 9 +++++++-- + 7 files changed, 34 insertions(+), 22 deletions(-) + +diff --git a/common.cpp b/common.cpp +index 3e5a2c8..203eda5 100644 +--- a/common.cpp ++++ b/common.cpp +@@ -2381,10 +2381,11 @@ static int check_runtime_path(const char * path) + } + + /** Return the path of an appropriate runtime data directory */ +-const char* common_get_runtime_path(void) ++std::string common_get_runtime_path() + { + const char *dir = getenv("XDG_RUNTIME_DIR"); + const char *uname = getenv("USER"); ++ std::string path; + + if (uname == NULL) + { +@@ -2396,19 +2397,19 @@ const char* common_get_runtime_path(void) + { + // /tmp/fish.user + dir = "/tmp/fish."; +- std::string path; + path.reserve(strlen(dir) + strlen(uname)); + path.append(dir); + path.append(uname); + if (check_runtime_path(path.c_str()) != 0) + { +- debug(0, L"Couldn't create secure runtime path: '%s'", path.c_str()); +- exit(EXIT_FAILURE); ++ debug(0, L"Runtime path not available. Try deleting the directory %s and restarting fish.", path.c_str()); ++ path.clear(); + } +- return strdup(path.c_str()); + } + else + { +- return dir; ++ path.reserve(strlen(dir)); ++ path.append(dir); + } ++ return path; + } +diff --git a/common.h b/common.h +index 4d18aca..b160245 100644 +--- a/common.h ++++ b/common.h +@@ -814,6 +814,6 @@ extern "C" { + } + + /** Return the path of an appropriate runtime data directory */ +-const char* common_get_runtime_path(void); ++std::string common_get_runtime_path(); + + #endif +diff --git a/env.cpp b/env.cpp +index 0bda417..703d619 100644 +--- a/env.cpp ++++ b/env.cpp +@@ -620,8 +620,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) + + const env_var_t user_dir_wstr = env_get_string(L"USER"); + +- const char * fishd_dir = common_get_runtime_path(); +- env_set(L"__fish_runtime_dir", str2wcstring(fishd_dir).c_str(), ENV_GLOBAL); ++ std::string fishd_dir = common_get_runtime_path(); ++ env_set(L"__fish_runtime_dir", str2wcstring(fishd_dir).c_str(), ENV_GLOBAL | ENV_EXPORT); + + wchar_t * user_dir = user_dir_wstr.missing()?NULL:const_cast(user_dir_wstr.c_str()); + +diff --git a/env_universal.cpp b/env_universal.cpp +index 1a97443..78e3130 100644 +--- a/env_universal.cpp ++++ b/env_universal.cpp +@@ -242,23 +242,29 @@ static void reconnect() + } + + +-void env_universal_init(const char * p, ++void env_universal_init(std::string p, + wchar_t *u, + void (*sf)(), + void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)) + { +- path=p; ++ path=p.c_str(); + user=u; + start_fishd=sf; + external_callback = cb; + +- env_universal_server.fd = get_socket(); +- env_universal_common_init(&callback); +- env_universal_read_all(); +- s_env_univeral_inited = true; +- if (env_universal_server.fd >= 0) ++ if (p == "") { ++ debug(1, L"Could not connect to universal variable server. You will not be able to share variable values between fish sessions."); ++ } ++ else + { +- env_universal_barrier(); ++ env_universal_server.fd = get_socket(); ++ env_universal_common_init(&callback); ++ env_universal_read_all(); ++ s_env_univeral_inited = true; ++ if (env_universal_server.fd >= 0) ++ { ++ env_universal_barrier(); ++ } + } + } + +diff --git a/env_universal.h b/env_universal.h +index 9e6ab85..f14db29 100644 +--- a/env_universal.h ++++ b/env_universal.h +@@ -17,7 +17,7 @@ extern connection_t env_universal_server; + /** + Initialize the envuni library + */ +-void env_universal_init(const char * p, ++void env_universal_init(std::string p, + wchar_t *u, + void (*sf)(), + void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)); +diff --git a/fish_pager.cpp b/fish_pager.cpp +index 27bc80e..6d05774 100644 +--- a/fish_pager.cpp ++++ b/fish_pager.cpp +@@ -1032,8 +1032,8 @@ static void init(int mangle_descriptors, int out) + exit(1); + } + +- +- env_universal_init("", 0, 0, 0); ++ std::string dir = common_get_runtime_path(); ++ env_universal_init(dir, 0, 0, 0); + input_common_init(&interrupt_handler); + output_set_writer(&pager_buffered_writer); + +diff --git a/fishd.cpp b/fishd.cpp +index dd43647..d725e43 100644 +--- a/fishd.cpp ++++ b/fishd.cpp +@@ -159,10 +159,15 @@ static int quit=0; + */ + static std::string get_socket_filename(void) + { +- const char *dir = common_get_runtime_path(); ++ std::string dir = common_get_runtime_path(); ++ ++ if (dir == "") { ++ debug(0, L"Cannot access desired socket path."); ++ exit(EXIT_FAILURE); ++ } + + std::string name; +- name.reserve(strlen(dir) + strlen(SOCK_FILENAME) + 1); ++ name.reserve(dir.length() + strlen(SOCK_FILENAME) + 1); + name.append(dir); + name.push_back('/'); + name.append(SOCK_FILENAME); +-- +1.9.3 + diff --git a/fish-upstream-CVE-2014-2905.patch b/fish-upstream-CVE-2014-2905.patch index 5a7bb2d..3f26d84 100644 --- a/fish-upstream-CVE-2014-2905.patch +++ b/fish-upstream-CVE-2014-2905.patch @@ -1,266 +1,478 @@ -From 8412c867a501e3a68e55fef6215e86d3ac9f617b Mon Sep 17 00:00:00 2001 -Message-Id: <8412c867a501e3a68e55fef6215e86d3ac9f617b.1398703637.git.luto@amacapital.net> -In-Reply-To: <3c5d5b344ee945b99e4bb16a44af6f293601813d.1398703637.git.luto@amacapital.net> -References: <3c5d5b344ee945b99e4bb16a44af6f293601813d.1398703637.git.luto@amacapital.net> +From 4cb4fc3ef889788b9755451bc565e27bb803b8ba Mon Sep 17 00:00:00 2001 +Message-Id: <4cb4fc3ef889788b9755451bc565e27bb803b8ba.1407803923.git.luto@amacapital.net> From: David Adam -Date: Sun, 20 Apr 2014 17:51:27 +0800 -Subject: [PATCH 3/4] Check effective credentials of socket peers +Date: Sun, 20 Apr 2014 19:20:07 +0800 +Subject: [PATCH] Fix for CVE-2014-2905 - fishd restart required. -Fix for CVE-2014-2905. + - Use a secure path for sockets (some code used under license from + tmux). + - Provide the secure path in the environment as $__fish_runtime_dir. + - Link the new path to the old path to ease migration from earlier + versions. -Code for getpeereid() on non-BSD systems imported from the PostgreSQL -project under a BSD-style license. +Closes #1359. + +After installing fish built from or after this commit, you MUST +terminate all running fishd processes (`killall fishd`, `pkill fishd` +or similar). Distributors are encouraged to do this from within their +packaging scripts. fishd will restart automatically, and no data should +be lost. --- - configure.ac | 4 +-- - doc_src/license.hdr | 30 +++++++++++++++++++- - env_universal.cpp | 9 ++++++ - fallback.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++- - fallback.h | 4 +++ - fishd.cpp | 9 +++++- - osx/config.h | 6 ++++ - 7 files changed, 137 insertions(+), 5 deletions(-) + common.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ + common.h | 2 ++ + doc_src/license.hdr | 20 ++++++++++++++ + env.cpp | 7 ++--- + env_universal.cpp | 41 +++++----------------------- + env_universal.h | 2 +- + env_universal_common.cpp | 10 +++++-- + env_universal_common.h | 9 +++++-- + fish_pager.cpp | 2 +- + fishd.cpp | 56 +++++++++++++++++++++++++++++++++++--- + 10 files changed, 172 insertions(+), 47 deletions(-) -diff --git a/configure.ac b/configure.ac -index ea7c592..bdfa5f0 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -557,7 +557,7 @@ LIBS=$LIBS_COMMON - # Check presense of various header files - # - --AC_CHECK_HEADERS([getopt.h termios.h sys/resource.h term.h ncurses/term.h ncurses.h curses.h stropts.h siginfo.h sys/select.h sys/ioctl.h execinfo.h spawn.h sys/sysctl.h]) -+AC_CHECK_HEADERS([getopt.h termios.h sys/resource.h term.h ncurses/term.h ncurses.h curses.h stropts.h siginfo.h sys/select.h sys/ioctl.h execinfo.h spawn.h sys/sysctl.h sys/un.h sys/ucred.h ucred.h ]) - - if test x$local_gettext != xno; then - AC_CHECK_HEADERS([libintl.h]) -@@ -698,7 +698,7 @@ fi - AC_CHECK_FUNCS( wcsdup wcsndup wcslen wcscasecmp wcsncasecmp fwprintf ) - AC_CHECK_FUNCS( futimes wcwidth wcswidth wcstok fputwc fgetwc ) - AC_CHECK_FUNCS( wcstol wcslcat wcslcpy lrand48_r killpg ) --AC_CHECK_FUNCS( backtrace backtrace_symbols sysconf getifaddrs ) -+AC_CHECK_FUNCS( backtrace backtrace_symbols sysconf getifaddrs getpeerucred getpeereid ) - - if test x$local_gettext != xno; then - AC_CHECK_FUNCS( gettext dcgettext ) -diff --git a/doc_src/license.hdr b/doc_src/license.hdr -index 64bab10..f292722 100644 ---- a/doc_src/license.hdr -+++ b/doc_src/license.hdr -@@ -1400,6 +1400,34 @@ POSSIBILITY OF SUCH DAMAGES. - -

+diff --git a/common.cpp b/common.cpp +index 7a9f7a5..3e5a2c8 100644 +--- a/common.cpp ++++ b/common.cpp +@@ -24,6 +24,7 @@ parts of fish. + #include + #include + #include ++#include --*/ -+


+ #ifdef HAVE_SYS_IOCTL_H + #include +@@ -2342,3 +2343,72 @@ char **make_null_terminated_array(const std::vector &lst) + { + return make_null_terminated_array_helper(lst); + } + -+

License for getpeereid

++/** ++ Check, and create if necessary, a secure runtime path ++ Derived from tmux.c in tmux (http://tmux.sourceforge.net/) ++*/ ++static int check_runtime_path(const char * path) ++{ ++ /* ++ * Copyright (c) 2007 Nicholas Marriott ++ * ++ * Permission to use, copy, modify, and distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER ++ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING ++ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ + -+\c fish contains code imported from the PostgreSQL project under -+license, namely the getpeereid fallback function. This code is copyrighted -+by: ++ struct stat statpath; ++ u_int uid = geteuid(); + -+Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group ++ if (mkdir(path, S_IRWXU) != 0 && errno != EEXIST) ++ return errno; ++ if (lstat(path, &statpath) != 0) ++ return errno; ++ if (!S_ISDIR(statpath.st_mode) ++ || statpath.st_uid != uid ++ || (statpath.st_mode & (S_IRWXG|S_IRWXO)) != 0) ++ return EACCES; ++ return 0; ++} + -+Portions Copyright (c) 1994, The Regents of the University of California ++/** Return the path of an appropriate runtime data directory */ ++const char* common_get_runtime_path(void) ++{ ++ const char *dir = getenv("XDG_RUNTIME_DIR"); ++ const char *uname = getenv("USER"); + -+Permission to use, copy, modify, and distribute this software and its -+documentation for any purpose, without fee, and without a written agreement -+is hereby granted, provided that the above copyright notice and this -+paragraph and the following two paragraphs appear in all copies. ++ if (uname == NULL) ++ { ++ const struct passwd *pw = getpwuid(getuid()); ++ uname = pw->pw_name; ++ } + -+IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR -+DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING -+LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS -+DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE -+POSSIBILITY OF SUCH DAMAGE. ++ if (dir == NULL) ++ { ++ // /tmp/fish.user ++ dir = "/tmp/fish."; ++ std::string path; ++ path.reserve(strlen(dir) + strlen(uname)); ++ path.append(dir); ++ path.append(uname); ++ if (check_runtime_path(path.c_str()) != 0) ++ { ++ debug(0, L"Couldn't create secure runtime path: '%s'", path.c_str()); ++ exit(EXIT_FAILURE); ++ } ++ return strdup(path.c_str()); ++ } ++ else ++ { ++ return dir; ++ } ++} +diff --git a/common.h b/common.h +index 57fe7fa..4d18aca 100644 +--- a/common.h ++++ b/common.h +@@ -813,5 +813,7 @@ extern "C" { + __attribute__((noinline)) void debug_thread_error(void); + } + ++/** Return the path of an appropriate runtime data directory */ ++const char* common_get_runtime_path(void); + + #endif +diff --git a/doc_src/license.hdr b/doc_src/license.hdr +index 64bab10..76981a9 100644 +--- a/doc_src/license.hdr ++++ b/doc_src/license.hdr +@@ -1400,6 +1400,26 @@ POSSIBILITY OF SUCH DAMAGES. + +

+ ++

License for code derived from tmux

++ ++Fish contains code derived from ++tmux, made available under an ISC ++license. ++

++Copyright (c) 2007 Nicholas Marriott ++

++Permission to use, copy, modify, and distribute this software for any ++purpose with or without fee is hereby granted, provided that the above ++copyright notice and this permission notice appear in all copies. ++

++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER ++IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING ++OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + -+THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, -+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -+AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS -+ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO -+PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ \htmlonly \endhtmlonly -+*/ +diff --git a/env.cpp b/env.cpp +index 13f87b6..0bda417 100644 +--- a/env.cpp ++++ b/env.cpp +@@ -57,7 +57,7 @@ + #include "complete.h" + + /** Command used to start fishd */ +-#define FISHD_CMD L"fishd ^ /tmp/fishd.log.%s" ++#define FISHD_CMD L"fishd ^ $__fish_runtime_dir/fishd.log.%s" + + // Version for easier debugging + //#define FISHD_CMD L"fishd" +@@ -618,10 +618,11 @@ void env_init(const struct config_paths_t *paths /* or NULL */) + env_set(L"version", version.c_str(), ENV_GLOBAL); + env_set(L"FISH_VERSION", version.c_str(), ENV_GLOBAL); + +- const env_var_t fishd_dir_wstr = env_get_string(L"FISHD_SOCKET_DIR"); + const env_var_t user_dir_wstr = env_get_string(L"USER"); + +- wchar_t * fishd_dir = fishd_dir_wstr.missing()?NULL:const_cast(fishd_dir_wstr.c_str()); ++ const char * fishd_dir = common_get_runtime_path(); ++ env_set(L"__fish_runtime_dir", str2wcstring(fishd_dir).c_str(), ENV_GLOBAL); ++ + wchar_t * user_dir = user_dir_wstr.missing()?NULL:const_cast(user_dir_wstr.c_str()); + + env_universal_init(fishd_dir , user_dir , diff --git a/env_universal.cpp b/env_universal.cpp -index c7d060a..987f88b 100644 +index c7d060a..1a97443 100644 --- a/env_universal.cpp +++ b/env_universal.cpp -@@ -88,6 +88,8 @@ static int try_get_socket_once(void) +@@ -61,7 +61,7 @@ static int get_socket_count = 0; + #define DEFAULT_RETRY_COUNT 15 + #define DEFAULT_RETRY_DELAY 0.2 - wdir = path; - wuname = user; -+ uid_t seuid; -+ gid_t segid; +-static wchar_t * path; ++static const char * path; + static wchar_t *user; + static void (*start_fishd)(); + static void (*external_callback)(fish_message_type_t type, const wchar_t *name, const wchar_t *val); +@@ -82,48 +82,19 @@ static int try_get_socket_once(void) + { + int s; +- wchar_t *wdir; +- wchar_t *wuname; +- char *dir = 0; +- +- wdir = path; +- wuname = user; +- if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { -@@ -135,6 +137,13 @@ static int try_get_socket_once(void) + wperror(L"socket"); return -1; } -+ if ((getpeereid(s, &seuid, &segid) != 0) || seuid != geteuid()) -+ { -+ debug(1, L"Wrong credentials for socket %s at fd %d", name.c_str(), s); -+ close(s); -+ return -1; -+ } -+ - if ((make_fd_nonblocking(s) != 0) || (fcntl(s, F_SETFD, FD_CLOEXEC) != 0)) - { - wperror(L"fcntl"); -diff --git a/fallback.cpp b/fallback.cpp -index 5e4b3e1..34db397 100644 ---- a/fallback.cpp -+++ b/fallback.cpp -@@ -15,8 +15,9 @@ - #include - #include - #include -+#include - #include --#include -+#include - #include - #include - #include -@@ -1521,3 +1522,80 @@ static int mk_wcswidth(const wchar_t *pwcs, size_t n) +- if (wdir) +- dir = wcs2str(wdir); +- else +- dir = strdup("/tmp"); +- +- std::string uname; +- if (wuname) +- { +- uname = wcs2string(wuname); +- } +- else +- { +- struct passwd *pw = getpwuid(getuid()); +- if (pw && pw->pw_name) +- { +- uname = pw->pw_name; +- } +- } +- + std::string name; +- name.reserve(strlen(dir) + uname.size() + strlen(SOCK_FILENAME) + 2); +- name.append(dir); +- name.append("/"); ++ name.reserve(strlen(path) + strlen(SOCK_FILENAME) + 1); ++ name.append(path); ++ name.push_back('/'); + name.append(SOCK_FILENAME); +- name.append(uname); +- +- free(dir); + +- debug(3, L"Connect to socket %s at fd %2", name.c_str(), s); ++ debug(3, L"Connect to socket %s at fd %d", name.c_str(), s); + + struct sockaddr_un local = {}; + local.sun_family = AF_UNIX; +@@ -271,7 +242,7 @@ static void reconnect() } - #endif // HAVE_BROKEN_WCWIDTH -+ -+#ifndef HAVE_GETPEEREID -+ -+/*------------------------------------------------------------------------- -+ * -+ * getpeereid.c -+ * get peer userid for UNIX-domain socket connection -+ * -+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group -+ * -+ * -+ * IDENTIFICATION -+ * src/port/getpeereid.c -+ * -+ *------------------------------------------------------------------------- -+ */ -+ -+#ifdef HAVE_SYS_UN_H -+#include -+#endif -+#ifdef HAVE_UCRED_H -+#include -+#endif -+#ifdef HAVE_SYS_UCRED_H -+#include + +-void env_universal_init(wchar_t * p, ++void env_universal_init(const char * p, + wchar_t *u, + void (*sf)(), + void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)) +diff --git a/env_universal.h b/env_universal.h +index 4f38fe7..9e6ab85 100644 +--- a/env_universal.h ++++ b/env_universal.h +@@ -17,7 +17,7 @@ extern connection_t env_universal_server; + /** + Initialize the envuni library + */ +-void env_universal_init(wchar_t * p, ++void env_universal_init(const char * p, + wchar_t *u, + void (*sf)(), + void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)); +diff --git a/env_universal_common.cpp b/env_universal_common.cpp +index f600e70..2b12cf1 100644 +--- a/env_universal_common.cpp ++++ b/env_universal_common.cpp +@@ -27,7 +27,6 @@ + #include + #include + #include +-#include + #include + + #ifdef HAVE_SYS_SELECT_H +@@ -87,6 +86,13 @@ + #define ENV_UNIVERSAL_EOF 0x102 + + /** ++ Maximum length of socket filename ++*/ ++#ifndef UNIX_PATH_MAX ++#define UNIX_PATH_MAX 100 +#endif + -+/* -+ * BSD-style getpeereid() for platforms that lack it. -+ */ -+int getpeereid(int sock, uid_t *uid, gid_t *gid) -+{ -+#if defined(SO_PEERCRED) -+ /* Linux: use getsockopt(SO_PEERCRED) */ -+ struct ucred peercred; -+ socklen_t so_len = sizeof(peercred); -+ -+ if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 || -+ so_len != sizeof(peercred)) -+ return -1; -+ *uid = peercred.uid; -+ *gid = peercred.gid; -+ return 0; -+#elif defined(LOCAL_PEERCRED) -+ /* Debian with FreeBSD kernel: use getsockopt(LOCAL_PEERCRED) */ -+ struct xucred peercred; -+ socklen_t * so_len = sizeof(peercred); -+ -+ if (getsockopt(sock, 0, LOCAL_PEERCRED, &peercred, &so_len) != 0 || -+ so_len != sizeof(peercred) || -+ peercred.cr_version != XUCRED_VERSION) -+ return -1; -+ *uid = peercred.cr_uid; -+ *gid = peercred.cr_gid; -+ return 0; -+#elif defined(HAVE_GETPEERUCRED) -+ /* Solaris: use getpeerucred() */ -+ ucred_t *ucred; -+ -+ ucred = NULL; /* must be initialized to NULL */ -+ if (getpeerucred(sock, &ucred) == -1) -+ return -1; -+ -+ *uid = ucred_geteuid(ucred); -+ *gid = ucred_getegid(ucred); -+ ucred_free(ucred); -+ -+ if (*uid == (uid_t) (-1) || *gid == (gid_t) (-1)) -+ return -1; -+ return 0; -+#else -+ /* No implementation available on this platform */ -+ errno = ENOSYS; -+ return -1; -+#endif -+} -+#endif // HAVE_GETPEEREID -diff --git a/fallback.h b/fallback.h -index eba91be..6898ea5 100644 ---- a/fallback.h -+++ b/fallback.h -@@ -482,3 +482,7 @@ double nan(char *tagp); ++/** + A variable entry. Stores the value of a variable and whether it + should be exported. Obviously, it needs to be allocated large + enough to fit the value string. +@@ -417,7 +423,7 @@ void env_universal_common_init(void (*cb)(fish_message_type_t type, const wchar_ + } + /** +- Read one byte of date form the specified connection ++ Read one byte of date from the specified connection + */ + static int read_byte(connection_t *src) + { +diff --git a/env_universal_common.h b/env_universal_common.h +index 0a13a41..deddfb3 100644 +--- a/env_universal_common.h ++++ b/env_universal_common.h +@@ -33,9 +33,9 @@ - #endif + + /** +- The filename to use for univeral variables. The username is appended ++ The filename to use for univeral variables. + */ +-#define SOCK_FILENAME "fishd.socket." ++#define SOCK_FILENAME "fishd.socket" + + /** + The different types of commands that can be sent between client/server +@@ -134,6 +134,11 @@ void try_send_all(connection_t *c); + message_t *create_message(fish_message_type_t type, const wchar_t *key, const wchar_t *val); + + /** ++ Constructs the fish socket filename ++*/ ++std::string env_universal_common_get_socket_filename(void); + -+#ifndef HAVE_GETPEEREID -+int getpeereid(int sock, uid_t *uid, gid_t *gid); -+#endif ++/** + Init the library + */ + void env_universal_common_init(void (*cb)(fish_message_type_t type, const wchar_t *key, const wchar_t *val)); +diff --git a/fish_pager.cpp b/fish_pager.cpp +index 9cde933..27bc80e 100644 +--- a/fish_pager.cpp ++++ b/fish_pager.cpp +@@ -1033,7 +1033,7 @@ static void init(int mangle_descriptors, int out) + } + + +- env_universal_init(0, 0, 0, 0); ++ env_universal_init("", 0, 0, 0); + input_common_init(&interrupt_handler); + output_set_writer(&pager_buffered_writer); + diff --git a/fishd.cpp b/fishd.cpp -index edb79c2..1e09524 100644 +index edb79c2..dd43647 100644 --- a/fishd.cpp +++ b/fishd.cpp -@@ -880,6 +880,8 @@ int main(int argc, char ** argv) - int child_socket; - struct sockaddr_un remote; - socklen_t t; -+ uid_t sock_euid; -+ gid_t sock_egid; - int max_fd; - int update_count=0; - -@@ -1000,7 +1002,12 @@ int main(int argc, char ** argv) - { - debug(4, L"Connected with new child on fd %d", child_socket); - -- if (make_fd_nonblocking(child_socket) != 0) -+ if (((getpeereid(child_socket, &sock_euid, &sock_egid) != 0) || sock_euid != geteuid())) -+ { -+ debug(1, L"Wrong credentials for child on fd %d", child_socket); -+ close(child_socket); -+ } -+ else if (make_fd_nonblocking(child_socket) != 0) - { - wperror(L"fcntl"); - close(child_socket); -diff --git a/osx/config.h b/osx/config.h -index 4968a78..bc058ae 100644 ---- a/osx/config.h -+++ b/osx/config.h -@@ -40,6 +40,12 @@ - /* Define to 1 if you have the header file. */ - #define HAVE_GETOPT_H 1 - -+/* Define to 1 if you have the `getpeereid' function. */ -+#define HAVE_GETPEEREID 1 +@@ -159,6 +159,27 @@ static int quit=0; + */ + static std::string get_socket_filename(void) + { ++ const char *dir = common_get_runtime_path(); ++ ++ std::string name; ++ name.reserve(strlen(dir) + strlen(SOCK_FILENAME) + 1); ++ name.append(dir); ++ name.push_back('/'); ++ name.append(SOCK_FILENAME); ++ ++ if (name.size() >= UNIX_PATH_MAX) ++ { ++ debug(1, L"Filename too long: '%s'", name.c_str()); ++ exit(EXIT_FAILURE); ++ } ++ return name; ++} ++ ++/** ++ Constructs the legacy socket filename ++*/ ++static std::string get_old_socket_filename(void) ++{ + const char *dir = getenv("FISHD_SOCKET_DIR"); + char *uname = getenv("USER"); + +@@ -174,10 +195,9 @@ static std::string get_socket_filename(void) + } + + std::string name; +- name.reserve(strlen(dir)+ strlen(uname)+ strlen(SOCK_FILENAME) + 1); ++ name.reserve(strlen(dir)+ strlen(uname)+ strlen("fishd.socket.") + 1); + name.append(dir); +- name.push_back('/'); +- name.append(SOCK_FILENAME); ++ name.append("/fishd.socket."); + name.append(uname); + + if (name.size() >= UNIX_PATH_MAX) +@@ -541,6 +561,7 @@ repeat: + int exitcode = EXIT_FAILURE; + struct sockaddr_un local; + const std::string sock_name = get_socket_filename(); ++ const std::string old_sock_name = get_old_socket_filename(); + + /* + Start critical section protected by lock +@@ -598,6 +619,19 @@ repeat: + doexit = 1; + } + ++ // Attempt to hardlink the old socket name so that old versions of fish keep working on upgrade ++ // Not critical if it fails ++ if (unlink(old_sock_name.c_str()) != 0 && errno != ENOENT) ++ { ++ debug(0, L"Could not create legacy socket path"); ++ wperror(L"unlink"); ++ } ++ else if (link(sock_name.c_str(), old_sock_name.c_str()) != 0) ++ { ++ debug(0, L"Could not create legacy socket path"); ++ wperror(L"link"); ++ } + -+/* Define to 1 if you have the `getpeerucred' function. */ -+/* #undef HAVE_GETPEERUCRED */ + unlock: + (void)unlink(lockfile.c_str()); + debug(4, L"Released lockfile: %s", lockfile.c_str()); +@@ -873,6 +907,18 @@ static void init() + } + + /** ++ Clean up behind ourselves ++*/ ++static void cleanup() ++{ ++ if (unlink(get_old_socket_filename().c_str()) != 0) ++ { ++ debug(0, L"Could not remove legacy socket path"); ++ wperror(L"unlink"); ++ } ++} + - /* Define to 1 if you have the `gettext' function. */ - /* #undef HAVE_GETTEXT */ ++/** + Main function for fishd + */ + int main(int argc, char ** argv) +@@ -973,6 +1019,7 @@ int main(int argc, char ** argv) + if (quit) + { + save(); ++ cleanup(); + exit(0); + } + +@@ -982,6 +1029,7 @@ int main(int argc, char ** argv) + if (errno != EINTR) + { + wperror(L"select"); ++ cleanup(); + exit(1); + } + } +@@ -994,6 +1042,7 @@ int main(int argc, char ** argv) + &t)) == -1) + { + wperror(L"accept"); ++ cleanup(); + exit(1); + } + else +@@ -1070,6 +1119,7 @@ int main(int argc, char ** argv) + { + debug(0, L"No more clients. Quitting"); + save(); ++ cleanup(); + break; + } -- -1.9.0 +1.9.3 diff --git a/fish-upstream-CVE-2014-2914-part2.patch b/fish-upstream-CVE-2014-2914-part2.patch new file mode 100644 index 0000000..3dae9ab --- /dev/null +++ b/fish-upstream-CVE-2014-2914-part2.patch @@ -0,0 +1,239 @@ +commit 397249a8d5a939d044da8ecfbb1654d48ce5a153 +Author: David Adam +Date: Mon Aug 4 13:34:26 2014 +0800 + + Authenticate connections to web_config service + + - Require all requests to use a session path. + - Use a redirect file to avoid exposing the URL on the command line, as + it contains the session path. + + Fix for CVE-2014-2914. + Closes #1438. + +diff --git a/share/tools/web_config/index.html b/share/tools/web_config/index.html +index 22cd470..90df114 100644 +--- a/share/tools/web_config/index.html ++++ b/share/tools/web_config/index.html +@@ -556,7 +556,7 @@ function switch_tab(new_tab) { + if (new_tab == 'tab_colors') { + /* Keep track of whether this is the first element */ + var first = true +- run_get_request('/colors/', function(key_and_values){ ++ run_get_request('colors/', function(key_and_values){ + /* Result is name, description, value */ + var key = key_and_values[0] + var description = key_and_values[1] +@@ -577,7 +577,7 @@ function switch_tab(new_tab) { + sample_prompts.length = 0 + /* Color the first one blue */ + var first = true; +- run_get_request('/sample_prompts/', function(sample_prompt){ ++ run_get_request('sample_prompts/', function(sample_prompt){ + var name = sample_prompt['name'] + sample_prompts[name] = sample_prompt + var color = first ? '66F' : 'AAA' +@@ -594,7 +594,7 @@ function switch_tab(new_tab) { + } else if (new_tab == 'tab_functions') { + /* Keep track of whether this is the first element */ + var first = true +- run_get_request('/functions/', function(contents){ ++ run_get_request('functions/', function(contents){ + var elem = create_master_element(contents, false/* description */, 'AAAAAA', '11pt', select_function_master_element) + if (first) { + /* It's the first element, so select it, so something gets selected */ +@@ -606,7 +606,7 @@ function switch_tab(new_tab) { + $('#master_detail_table').show() + wants_data_table = false + } else if (new_tab == 'tab_variables') { +- run_get_request_with_bulk_handler('/variables/', function(json_contents){ ++ run_get_request_with_bulk_handler('variables/', function(json_contents){ + var rows = new Array() + for (var i = 0; i < json_contents.length; i++) { + var contents = json_contents[i] +@@ -622,7 +622,7 @@ function switch_tab(new_tab) { + } else if (new_tab == 'tab_history') { + // Clear the history map + history_element_map.length = 0 +- run_get_request_with_bulk_handler('/history/', function(json_contents){ ++ run_get_request_with_bulk_handler('history/', function(json_contents){ + start = new Date().getTime() + var rows = new Array() + for (var i = 0; i < json_contents.length; i++) { +@@ -757,7 +757,7 @@ function select_color_master_element(elem) { + function select_function_master_element(elem) { + select_master_element(elem) + +- run_post_request('/get_function/', { ++ run_post_request('get_function/', { + what: current_master_element_name() + }, function(contents){ + /* Replace leading tabs and groups of four spaces at the beginning of a line with two spaces. */ +@@ -773,7 +773,7 @@ function select_sample_prompt_master_element(elem) { + select_master_element(elem) + var name = current_master_element_name() + sample_prompt = sample_prompts[name] +- run_post_request('/get_sample_prompt/', { ++ run_post_request('get_sample_prompt/', { + what: sample_prompt['function'] + }, function(keys_and_values){ + var prompt_func = keys_and_values['function'] +@@ -788,7 +788,7 @@ function select_sample_prompt_master_element(elem) { + function select_current_prompt_master_element(elem) { + $('.prompt_save_button').hide() + select_master_element(elem) +- run_get_request_with_bulk_handler('/current_prompt/', function(keys_and_values){ ++ run_get_request_with_bulk_handler('current_prompt/', function(keys_and_values){ + var prompt_func = keys_and_values['function'] + var prompt_demo = keys_and_values['demo'] + var prompt_font_size = keys_and_values['font_size'] +@@ -801,7 +801,7 @@ function select_current_prompt_master_element(elem) { + function save_current_prompt() { + var name = current_master_element_name() + var sample_prompt = sample_prompts[name] +- run_post_request('/set_prompt/', { ++ run_post_request('set_prompt/', { + what: sample_prompt['function'] + }, function(contents){ + if (contents == "OK") { +@@ -817,7 +817,7 @@ function post_style_to_server() { + if (! style) + return + +- run_post_request('/set_color/', { ++ run_post_request('set_color/', { + what: current_master_element_name(), + color: style.color, + background_color: style.background_color, +@@ -1221,7 +1221,7 @@ function escape_HTML(foo) { + function tell_fish_to_delete_element(idx) { + var row_elem = $('#data_table_row_' + idx) + var txt = history_element_map[idx] +- run_post_request('/delete_history_item/', { ++ run_post_request('delete_history_item/', { + what: txt + }, function(contents){ + if (contents == "OK") { +diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py +index 1b9250b..2a103eb 100755 +--- a/share/tools/web_config/webconfig.py ++++ b/share/tools/web_config/webconfig.py +@@ -17,7 +17,7 @@ else: + from urllib.parse import parse_qs + import webbrowser + import subprocess +-import re, socket, os, sys, cgi, select, time, glob ++import re, socket, os, sys, cgi, select, time, glob, random, string + try: + import json + except ImportError: +@@ -485,9 +485,16 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + else: font_size = '18pt' + return font_size + +- + def do_GET(self): + p = self.path ++ ++ authpath = '/' + authkey ++ if p.startswith(authpath): ++ p = p[len(authpath):] ++ else: ++ return self.send_error(403) ++ self.path = p ++ + if p == '/colors/': + output = self.do_get_colors() + elif p == '/functions/': +@@ -519,6 +526,14 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + + def do_POST(self): + p = self.path ++ ++ authpath = '/' + authkey ++ if p.startswith(authpath): ++ p = p[len(authpath):] ++ else: ++ return self.send_error(403) ++ self.path = p ++ + if IS_PY2: + ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) + else: # Python 3 +@@ -582,7 +597,19 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def log_request(self, code='-', size='-'): + """ Disable request logging """ + pass +- ++ ++redirect_template_html = """ ++ ++ ++ ++ ++ ++ ++

Start the Fish Web config

++ ++ ++""" ++ + # find fish + fish_bin_dir = os.environ.get('__fish_bin_dir') + fish_bin_path = None +@@ -618,6 +645,9 @@ initial_wd = os.getcwd() + where = os.path.dirname(sys.argv[0]) + os.chdir(where) + ++# Generate a 16-byte random key as a hexadecimal string ++authkey = hex(random.getrandbits(16*4))[2:] ++ + # Try to find a suitable port + PORT = 8000 + while PORT <= 9000: +@@ -647,9 +677,36 @@ if len(sys.argv) > 1: + initial_tab = '#' + tab + break + +-url = 'http://localhost:%d/%s' % (PORT, initial_tab) +-print("Web config started at '%s'. Hit enter to stop." % url) +-webbrowser.open(url) ++url = 'http://localhost:%d/%s/%s' % (PORT, authkey, initial_tab) ++ ++# Create temporary file to hold redirect to real server ++# This prevents exposing the URL containing the authentication key on the command line ++# (see CVE-2014-2914 or https://github.com/fish-shell/fish-shell/issues/1438) ++if 'XDG_CACHE_HOME' in os.environ: ++ dirname = os.path.expanduser(os.path.expandvars('$XDG_CACHE_HOME/fish/')) ++else: ++ dirname = os.path.expanduser('~/.cache/fish/') ++ ++os.umask(0o0077) ++try: ++ os.makedirs(dirname, 0o0700) ++except OSError as e: ++ if e.errno == 17: ++ pass ++ else: ++ raise e ++ ++randtoken = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) ++filename = dirname + 'web_config-%s.html' % randtoken ++ ++f = open(filename, 'w') ++f.write(redirect_template_html % (url, url)) ++f.close() ++ ++# Open temporary file as URL ++fileurl = 'file://' + filename ++print("Web config started at '%s'. Hit enter to stop." % fileurl) ++webbrowser.open(fileurl) + + # Select on stdin and httpd + stdin_no = sys.stdin.fileno() +@@ -666,3 +723,5 @@ try: + except KeyboardInterrupt: + print("\nShutting down.") + ++# Clean up temporary file ++os.remove(filename) diff --git a/fish-webconfig-CVE-2014-2914-followup-1.patch b/fish-webconfig-CVE-2014-2914-followup-1.patch new file mode 100644 index 0000000..9fab859 --- /dev/null +++ b/fish-webconfig-CVE-2014-2914-followup-1.patch @@ -0,0 +1,45 @@ +commit 78e2b7cc0897de3eb2a8cdc0f5efe49a34402f9d +Author: Andy Lutomirski +Date: Mon Aug 11 17:50:56 2014 -0700 + + webconfig: Use a constant-time token comparison + + This prevents a linear-time attack to recover the auth token. + +diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py +index 2a103eb..452f771 100755 +--- a/share/tools/web_config/webconfig.py ++++ b/share/tools/web_config/webconfig.py +@@ -471,6 +471,14 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + # Ignore unreadable files, etc + pass + return result ++ ++ def secure_startswith(self, haystack, needle): ++ if len(haystack) < len(needle): ++ return False ++ bits = 0 ++ for x,y in zip(haystack, needle): ++ bits |= ord(x) ^ ord(y) ++ return bits == 0 + + def font_size_for_ansi_prompt(self, prompt_demo_ansi): + width = ansi_prompt_line_width(prompt_demo_ansi) +@@ -489,7 +497,7 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + p = self.path + + authpath = '/' + authkey +- if p.startswith(authpath): ++ if self.secure_startswith(p, authpath): + p = p[len(authpath):] + else: + return self.send_error(403) +@@ -528,7 +536,7 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + p = self.path + + authpath = '/' + authkey +- if p.startswith(authpath): ++ if self.secure_startswith(p, authpath): + p = p[len(authpath):] + else: + return self.send_error(403) diff --git a/fish-webconfig-CVE-2014-2914-followup-2.patch b/fish-webconfig-CVE-2014-2914-followup-2.patch new file mode 100644 index 0000000..a2b3c27 --- /dev/null +++ b/fish-webconfig-CVE-2014-2914-followup-2.patch @@ -0,0 +1,19 @@ +commit 236a0ce46819ce4b93f73ba112e3bfb0726bade7 +Author: Andy Lutomirski +Date: Mon Aug 11 17:51:27 2014 -0700 + + webconfig: Use 16 byte tokens, as advertized + +diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py +index e129c76..2ceb67e 100755 +--- a/share/tools/web_config/webconfig.py ++++ b/share/tools/web_config/webconfig.py +@@ -654,7 +654,7 @@ where = os.path.dirname(sys.argv[0]) + os.chdir(where) + + # Generate a 16-byte random key as a hexadecimal string +-authkey = hex(random.getrandbits(16*4))[2:] ++authkey = hex(random.getrandbits(16*8))[2:] + + # Try to find a suitable port + PORT = 8000 diff --git a/fish-webconfig-CVE-2014-2914-followup-3.patch b/fish-webconfig-CVE-2014-2914-followup-3.patch new file mode 100644 index 0000000..300f54b --- /dev/null +++ b/fish-webconfig-CVE-2014-2914-followup-3.patch @@ -0,0 +1,21 @@ +commit f5d81d3beac2542d675af15bf7f71762c456f30d +Author: Andy Lutomirski +Date: Mon Aug 11 17:52:27 2014 -0700 + + webconfig: Get the auth token from os.urandom + + random.getrandbits shouldn't be used for security. + +diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py +index 2ceb67e..f36f63f 100755 +--- a/share/tools/web_config/webconfig.py ++++ b/share/tools/web_config/webconfig.py +@@ -654,7 +654,7 @@ where = os.path.dirname(sys.argv[0]) + os.chdir(where) + + # Generate a 16-byte random key as a hexadecimal string +-authkey = hex(random.getrandbits(16*8))[2:] ++authkey = hex(os.urandom(16))[2:] + + # Try to find a suitable port + PORT = 8000 diff --git a/fish.spec b/fish.spec index 236aef4..8a96b26 100644 --- a/fish.spec +++ b/fish.spec @@ -1,6 +1,6 @@ Name: fish Version: 2.1.0 -Release: 10%{?dist} +Release: 11%{?dist} Summary: A friendly interactive shell Group: System Environment/Shells @@ -11,8 +11,13 @@ Patch0: fish-remove-usr-local.patch Patch1: fish-add-link-cxxflags.patch Patch2: fish-use-usrbinpython.patch Patch3: fish-upstream-CVE-2014-2914.patch -Patch4: fish-upstream-CVE-2014-2905.patch -Patch5: fish-upstream-CVE-2014-2906.patch +Patch4: fish-upstream-CVE-2014-2914-part2.patch +Patch5: fish-upstream-CVE-2014-2905.patch +Patch6: fish-upstream-CVE-2014-2905-part2.patch +Patch7: fish-upstream-CVE-2014-2906.patch +Patch8: fish-webconfig-CVE-2014-2914-followup-1.patch +Patch9: fish-webconfig-CVE-2014-2914-followup-2.patch +Patch10: fish-webconfig-CVE-2014-2914-followup-3.patch BuildRequires: ncurses-devel gettext groff doxygen @@ -86,6 +91,9 @@ fi %changelog +* Tue Aug 12 2014 Andy Lutomirski - 2.1.0-11 +- Improve fixes for CVE-2014-2905 and CVE-2014-2914 + * Sat Jun 07 2014 Fedora Release Engineering - 2.1.0-10 - Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild