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.
This commit is contained in:
David Adam 2014-04-20 19:20:07 +08:00
parent af14cf8f8b
commit 4cb4fc3ef8
10 changed files with 172 additions and 47 deletions

View file

@ -24,6 +24,7 @@ parts of fish.
#include <stdio.h> #include <stdio.h>
#include <dirent.h> #include <dirent.h>
#include <sys/types.h> #include <sys/types.h>
#include <pwd.h>
#ifdef HAVE_SYS_IOCTL_H #ifdef HAVE_SYS_IOCTL_H
#include <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); 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 <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.
*/
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;
}
}

View file

@ -813,5 +813,7 @@ extern "C" {
__attribute__((noinline)) void debug_thread_error(void); __attribute__((noinline)) void debug_thread_error(void);
} }
/** Return the path of an appropriate runtime data directory */
const char* common_get_runtime_path(void);
#endif #endif

View file

@ -1400,6 +1400,26 @@ POSSIBILITY OF SUCH DAMAGES.
<P> <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.
*/ */
\htmlonly </div> \endhtmlonly \htmlonly </div> \endhtmlonly

View file

@ -57,7 +57,7 @@
#include "complete.h" #include "complete.h"
/** Command used to start fishd */ /** 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 // Version for easier debugging
//#define FISHD_CMD L"fishd" //#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"version", version.c_str(), ENV_GLOBAL);
env_set(L"FISH_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"); 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()); 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 , env_universal_init(fishd_dir , user_dir ,

View file

@ -61,7 +61,7 @@ static int get_socket_count = 0;
#define DEFAULT_RETRY_COUNT 15 #define DEFAULT_RETRY_COUNT 15
#define DEFAULT_RETRY_DELAY 0.2 #define DEFAULT_RETRY_DELAY 0.2
static wchar_t * path; static const char * path;
static wchar_t *user; static wchar_t *user;
static void (*start_fishd)(); static void (*start_fishd)();
static void (*external_callback)(fish_message_type_t type, const wchar_t *name, const wchar_t *val); 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; int s;
wchar_t *wdir;
wchar_t *wuname;
char *dir = 0;
wdir = path;
wuname = user;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
{ {
wperror(L"socket"); wperror(L"socket");
return -1; 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; std::string name;
name.reserve(strlen(dir) + uname.size() + strlen(SOCK_FILENAME) + 2); name.reserve(strlen(path) + strlen(SOCK_FILENAME) + 1);
name.append(dir); name.append(path);
name.append("/"); name.push_back('/');
name.append(SOCK_FILENAME); name.append(SOCK_FILENAME);
name.append(uname);
free(dir); debug(3, L"Connect to socket %s at fd %d", name.c_str(), s);
debug(3, L"Connect to socket %s at fd %2", name.c_str(), s);
struct sockaddr_un local = {}; struct sockaddr_un local = {};
local.sun_family = AF_UNIX; 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, wchar_t *u,
void (*sf)(), void (*sf)(),
void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)) void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val))

View file

@ -17,7 +17,7 @@ extern connection_t env_universal_server;
/** /**
Initialize the envuni library Initialize the envuni library
*/ */
void env_universal_init(wchar_t * p, void env_universal_init(const char * p,
wchar_t *u, wchar_t *u,
void (*sf)(), void (*sf)(),
void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val)); void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val));

View file

@ -27,7 +27,6 @@
#include <locale.h> #include <locale.h>
#include <dirent.h> #include <dirent.h>
#include <signal.h> #include <signal.h>
#include <sys/stat.h>
#include <map> #include <map>
#ifdef HAVE_SYS_SELECT_H #ifdef HAVE_SYS_SELECT_H
@ -86,6 +85,13 @@
*/ */
#define ENV_UNIVERSAL_EOF 0x102 #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 A variable entry. Stores the value of a variable and whether it
should be exported. Obviously, it needs to be allocated large should be exported. Obviously, it needs to be allocated large
@ -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) static int read_byte(connection_t *src)
{ {

View file

@ -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 The different types of commands that can be sent between client/server
@ -133,6 +133,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); 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 Init the library
*/ */

View file

@ -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); input_common_init(&interrupt_handler);
output_set_writer(&pager_buffered_writer); output_set_writer(&pager_buffered_writer);

View file

@ -158,6 +158,27 @@ static int quit=0;
Constructs the fish socket filename Constructs the fish socket filename
*/ */
static std::string get_socket_filename(void) 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"); const char *dir = getenv("FISHD_SOCKET_DIR");
char *uname = getenv("USER"); char *uname = getenv("USER");
@ -174,10 +195,9 @@ static std::string get_socket_filename(void)
} }
std::string name; 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.append(dir);
name.push_back('/'); name.append("/fishd.socket.");
name.append(SOCK_FILENAME);
name.append(uname); name.append(uname);
if (name.size() >= UNIX_PATH_MAX) if (name.size() >= UNIX_PATH_MAX)
@ -541,6 +561,7 @@ repeat:
int exitcode = EXIT_FAILURE; int exitcode = EXIT_FAILURE;
struct sockaddr_un local; struct sockaddr_un local;
const std::string sock_name = get_socket_filename(); const std::string sock_name = get_socket_filename();
const std::string old_sock_name = get_old_socket_filename();
/* /*
Start critical section protected by lock Start critical section protected by lock
@ -598,6 +619,19 @@ repeat:
doexit = 1; 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: unlock:
(void)unlink(lockfile.c_str()); (void)unlink(lockfile.c_str());
debug(4, L"Released lockfile: %s", lockfile.c_str()); debug(4, L"Released lockfile: %s", lockfile.c_str());
@ -872,6 +906,18 @@ static void init()
load(); load();
} }
/**
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 Main function for fishd
*/ */
@ -973,6 +1019,7 @@ int main(int argc, char ** argv)
if (quit) if (quit)
{ {
save(); save();
cleanup();
exit(0); exit(0);
} }
@ -982,6 +1029,7 @@ int main(int argc, char ** argv)
if (errno != EINTR) if (errno != EINTR)
{ {
wperror(L"select"); wperror(L"select");
cleanup();
exit(1); exit(1);
} }
} }
@ -994,6 +1042,7 @@ int main(int argc, char ** argv)
&t)) == -1) &t)) == -1)
{ {
wperror(L"accept"); wperror(L"accept");
cleanup();
exit(1); exit(1);
} }
else else
@ -1070,6 +1119,7 @@ int main(int argc, char ** argv)
{ {
debug(0, L"No more clients. Quitting"); debug(0, L"No more clients. Quitting");
save(); save();
cleanup();
break; break;
} }