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 19:20:07 +0800 Subject: [PATCH] Fix for CVE-2014-2905 - fishd restart required. - 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. 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. --- 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/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); } + +/** + 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. + */ + + struct stat statpath; + u_int uid = geteuid(); + + 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; +} + +/** 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"); + + if (uname == NULL) + { + const struct passwd *pw = getpwuid(getuid()); + uname = pw->pw_name; + } + + 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. + */ \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..1a97443 100644 --- a/env_universal.cpp +++ b/env_universal.cpp @@ -61,7 +61,7 @@ static int get_socket_count = 0; #define DEFAULT_RETRY_COUNT 15 #define DEFAULT_RETRY_DELAY 0.2 -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) { wperror(L"socket"); return -1; } - 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() } -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 + +/** 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 @@ /** - 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); + +/** 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..dd43647 100644 --- a/fishd.cpp +++ b/fishd.cpp @@ -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"); + } + 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"); + } +} + +/** 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.3