Improve fixes for CVE-2014-2905 and CVE-2014-2914

This includes upstream work and my own followups.
epel9
Andy Lutomirski 11 years ago
parent 16cec3d48d
commit 4de0b65d6d

@ -0,0 +1,185 @@
From b5cd21c337a8990c0c343ab2c22d3dc123a03d25 Mon Sep 17 00:00:00 2001
Message-Id: <b5cd21c337a8990c0c343ab2c22d3dc123a03d25.1407803973.git.luto@amacapital.net>
From: David Adam <zanchey@ucc.gu.uwa.edu.au>
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<wchar_t*>(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

@ -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 <zanchey@ucc.gu.uwa.edu.au>
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.
<P>
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 <stdio.h>
#include <dirent.h>
#include <sys/types.h>
+#include <pwd.h>
-*/
+<hr>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
@@ -2342,3 +2343,72 @@ char **make_null_terminated_array(const std::vector<std::string> &lst)
{
return make_null_terminated_array_helper(lst);
}
+
+<h2>License for getpeereid</h2>
+/**
+ 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 <nicm@users.sourceforge.net>
+ *
+ * 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.
<P>
+<h2>License for code derived from tmux</h2>
+
+Fish contains code derived from
+<a href="http://tmux.sourceforge.net">tmux</a>, made available under an ISC
+license.
+<p>
+Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+<p>
+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.
+<p>
+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 </div> \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<wchar_t*>(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<wchar_t*>(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 <stdio.h>
#include <unistd.h>
#include <sys/types.h>
+#include <sys/socket.h>
#include <sys/stat.h>
-#include <unistd.h>
+#include <sys/param.h>
#include <errno.h>
#include <fcntl.h>
#include <wchar.h>
@@ -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 <sys/un.h>
+#endif
+#ifdef HAVE_UCRED_H
+#include <ucred.h>
+#endif
+#ifdef HAVE_SYS_UCRED_H
+#include <sys/ucred.h>
-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 <locale.h>
#include <dirent.h>
#include <signal.h>
-#include <sys/stat.h>
#include <map>
#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 <getopt.h> 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

@ -0,0 +1,239 @@
commit 397249a8d5a939d044da8ecfbb1654d48ce5a153
Author: David Adam <zanchey@ucc.gu.uwa.edu.au>
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 = """
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="refresh" content="0;URL='%s'" />
+ </head>
+ <body>
+ <p><a href="%s">Start the Fish Web config</a></p>
+ </body>
+</html>
+"""
+
# 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)

@ -0,0 +1,45 @@
commit 78e2b7cc0897de3eb2a8cdc0f5efe49a34402f9d
Author: Andy Lutomirski <luto@amacapital.net>
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)

@ -0,0 +1,19 @@
commit 236a0ce46819ce4b93f73ba112e3bfb0726bade7
Author: Andy Lutomirski <luto@amacapital.net>
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

@ -0,0 +1,21 @@
commit f5d81d3beac2542d675af15bf7f71762c456f30d
Author: Andy Lutomirski <luto@amacapital.net>
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

@ -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 <luto@mit.edu> - 2.1.0-11
- Improve fixes for CVE-2014-2905 and CVE-2014-2914
* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2.1.0-10
- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild

Loading…
Cancel
Save