From 10642a34f17ae45bd93be3ae6021ee920d3da0c2 Mon Sep 17 00:00:00 2001 From: Anders Bergh Date: Tue, 4 Mar 2014 09:59:26 +0100 Subject: [PATCH 01/14] fish_config: Listen on both IPv6 and IPv4. A subclass of TCPServer was created to deny any non-local connections and to listen using an IPv6 socket. --- share/tools/web_config/webconfig.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index f735a0268..1b9250b17 100755 --- a/share/tools/web_config/webconfig.py +++ b/share/tools/web_config/webconfig.py @@ -250,6 +250,16 @@ class FishVar: if self.exported: flags.append('exported') return [self.name, self.value, ', '.join(flags)] +class FishConfigTCPServer(SocketServer.TCPServer): + """TCPServer that only accepts connections from localhost (IPv4/IPv6).""" + WHITELIST = set(['::1', '::ffff:127.0.0.1', '127.0.0.1']) + + address_family = socket.AF_INET6 + + def verify_request(self, request, client_address): + return client_address[0] in FishConfigTCPServer.WHITELIST + + class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def write_to_wfile(self, txt): @@ -613,7 +623,7 @@ PORT = 8000 while PORT <= 9000: try: Handler = FishConfigHTTPRequestHandler - httpd = SocketServer.TCPServer(("", PORT), Handler) + httpd = FishConfigTCPServer(("::", PORT), Handler) # Success break except socket.error: From 8412c867a501e3a68e55fef6215e86d3ac9f617b Mon Sep 17 00:00:00 2001 From: David Adam Date: Sun, 20 Apr 2014 17:51:27 +0800 Subject: [PATCH 02/14] Check effective credentials of socket peers Fix for CVE-2014-2905. Code for getpeereid() on non-BSD systems imported from the PostgreSQL project under a BSD-style license. --- 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(-) diff --git a/configure.ac b/configure.ac index ea7c592f2..bdfa5f0ef 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 64bab10f0..f292722bc 100644 --- a/doc_src/license.hdr +++ b/doc_src/license.hdr @@ -1400,6 +1400,34 @@ POSSIBILITY OF SUCH DAMAGES.

-*/ +


+ +

License for getpeereid

+ +\c fish contains code imported from the PostgreSQL project under +license, namely the getpeereid fallback function. This code is copyrighted +by: + +Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + +Portions Copyright (c) 1994, The Regents of the University of California + +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. + +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. + +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_universal.cpp b/env_universal.cpp index c7d060ad7..987f88b0d 100644 --- a/env_universal.cpp +++ b/env_universal.cpp @@ -88,6 +88,8 @@ static int try_get_socket_once(void) wdir = path; wuname = user; + uid_t seuid; + gid_t segid; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { @@ -135,6 +137,13 @@ static int try_get_socket_once(void) 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 5e4b3e1b2..34db3973b 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) } #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 +#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 eba91be6c..6898ea576 100644 --- a/fallback.h +++ b/fallback.h @@ -482,3 +482,7 @@ double nan(char *tagp); #endif + +#ifndef HAVE_GETPEEREID +int getpeereid(int sock, uid_t *uid, gid_t *gid); +#endif diff --git a/fishd.cpp b/fishd.cpp index edb79c22f..1e0952447 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 4968a78b2..bc058ae63 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 + +/* Define to 1 if you have the `getpeerucred' function. */ +/* #undef HAVE_GETPEERUCRED */ + /* Define to 1 if you have the `gettext' function. */ /* #undef HAVE_GETTEXT */ From c0989dce2d882c94eb3183e7b94402ba53534abb Mon Sep 17 00:00:00 2001 From: David Adam Date: Sun, 20 Apr 2014 23:51:20 +0800 Subject: [PATCH 03/14] use mktemp(1) to generate temporary file names Fix for CVE-2014-2906. Closes a race condition in funced which would allow execution of arbitrary code; closes a race condition in psub which would allow alternation of the data stream. Note that `psub -f` does not work (#1040); a fix should be committed separately for ease of maintenance. --- share/functions/funced.fish | 6 +----- share/functions/psub.fish | 11 +++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/share/functions/funced.fish b/share/functions/funced.fish index 3c2de0614..ca2e27721 100644 --- a/share/functions/funced.fish +++ b/share/functions/funced.fish @@ -81,11 +81,7 @@ function funced --description 'Edit function definition' return 0 end - set -q TMPDIR; or set -l TMPDIR /tmp - set -l tmpname (printf "$TMPDIR/fish_funced_%d_%d.fish" %self (random)) - while test -f $tmpname - set tmpname (printf "$TMPDIR/fish_funced_%d_%d.fish" %self (random)) - end + set tmpname (mktemp -t fish_funced.XXXXXXXXXX) if functions -q -- $funcname functions -- $funcname > $tmpname diff --git a/share/functions/psub.fish b/share/functions/psub.fish index 42e34c731..7877aa4ec 100644 --- a/share/functions/psub.fish +++ b/share/functions/psub.fish @@ -45,21 +45,16 @@ function psub --description "Read from stdin into a file and output the filename return end - # Find unique file name for writing output to - while true - set filename /tmp/.psub.(echo %self).(random); - if not test -e $filename - break; - end - end - if test use_fifo = 1 # Write output to pipe. This needs to be done in the background so # that the command substitution exits without needing to wait for # all the commands to exit + set dir (mktemp -d /tmp/.psub.XXXXXXXXXX); or return + set filename $dir/psub.fifo mkfifo $filename cat >$filename & else + set filename (mktemp /tmp/.psub.XXXXXXXXXX) cat >$filename end From af14cf8f8bedd69c5ce3259758ba96ffd2ca7b4e Mon Sep 17 00:00:00 2001 From: David Adam Date: Thu, 31 Jul 2014 16:57:42 +0800 Subject: [PATCH 04/14] Revert "Check effective credentials of socket peers" This reverts commit 8412c867a501e3a68e55fef6215e86d3ac9f617b. Just checking the credentials of the peer turns out to be insufficient. See https://github.com/fish-shell/fish-shell/issues/1436. --- 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, 5 insertions(+), 137 deletions(-) diff --git a/configure.ac b/configure.ac index bdfa5f0ef..ea7c592f2 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 sys/un.h sys/ucred.h ucred.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]) 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 getpeerucred getpeereid ) +AC_CHECK_FUNCS( backtrace backtrace_symbols sysconf getifaddrs ) 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 f292722bc..64bab10f0 100644 --- a/doc_src/license.hdr +++ b/doc_src/license.hdr @@ -1400,34 +1400,6 @@ POSSIBILITY OF SUCH DAMAGES.

-


- -

License for getpeereid

- -\c fish contains code imported from the PostgreSQL project under -license, namely the getpeereid fallback function. This code is copyrighted -by: - -Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group - -Portions Copyright (c) 1994, The Regents of the University of California - -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. - -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. - -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_universal.cpp b/env_universal.cpp index 987f88b0d..c7d060ad7 100644 --- a/env_universal.cpp +++ b/env_universal.cpp @@ -88,8 +88,6 @@ static int try_get_socket_once(void) wdir = path; wuname = user; - uid_t seuid; - gid_t segid; if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { @@ -137,13 +135,6 @@ static int try_get_socket_once(void) 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 34db3973b..5e4b3e1b2 100644 --- a/fallback.cpp +++ b/fallback.cpp @@ -15,9 +15,8 @@ #include #include #include -#include #include -#include +#include #include #include #include @@ -1522,80 +1521,3 @@ static int mk_wcswidth(const wchar_t *pwcs, size_t n) } #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 -#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 6898ea576..eba91be6c 100644 --- a/fallback.h +++ b/fallback.h @@ -482,7 +482,3 @@ double nan(char *tagp); #endif - -#ifndef HAVE_GETPEEREID -int getpeereid(int sock, uid_t *uid, gid_t *gid); -#endif diff --git a/fishd.cpp b/fishd.cpp index 1e0952447..edb79c22f 100644 --- a/fishd.cpp +++ b/fishd.cpp @@ -880,8 +880,6 @@ 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; @@ -1002,12 +1000,7 @@ int main(int argc, char ** argv) { debug(4, L"Connected with new child on fd %d", child_socket); - 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) + if (make_fd_nonblocking(child_socket) != 0) { wperror(L"fcntl"); close(child_socket); diff --git a/osx/config.h b/osx/config.h index bc058ae63..4968a78b2 100644 --- a/osx/config.h +++ b/osx/config.h @@ -40,12 +40,6 @@ /* 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 - -/* Define to 1 if you have the `getpeerucred' function. */ -/* #undef HAVE_GETPEERUCRED */ - /* Define to 1 if you have the `gettext' function. */ /* #undef HAVE_GETTEXT */ From 4cb4fc3ef889788b9755451bc565e27bb803b8ba Mon Sep 17 00:00:00 2001 From: David Adam Date: Sun, 20 Apr 2014 19:20:07 +0800 Subject: [PATCH 05/14] 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 7a9f7a514..3e5a2c8d6 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 57fe7fa1a..4d18aca89 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 64bab10f0..76981a9db 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 13f87b6cc..0bda4178c 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 c7d060ad7..1a9744393 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 4f38fe796..9e6ab85a1 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 f600e70a9..2b12cf108 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 @@ -86,6 +85,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 @@ -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 0a13a4187..deddfb39b 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 @@ -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); +/** + Constructs the fish socket filename +*/ +std::string env_universal_common_get_socket_filename(void); + /** Init the library */ diff --git a/fish_pager.cpp b/fish_pager.cpp index 9cde933e2..27bc80e51 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 edb79c22f..dd4364774 100644 --- a/fishd.cpp +++ b/fishd.cpp @@ -158,6 +158,27 @@ static int quit=0; Constructs the fish socket filename */ 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()); @@ -872,6 +906,18 @@ static void init() 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 */ @@ -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; } From b5cd21c337a8990c0c343ab2c22d3dc123a03d25 Mon Sep 17 00:00:00 2001 From: David Adam Date: Mon, 4 Aug 2014 13:26:14 +0800 Subject: [PATCH 06/14] 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 3e5a2c8d6..203eda52c 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 4d18aca89..b1602458b 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 0bda4178c..703d619c7 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 1a9744393..78e3130aa 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 9e6ab85a1..f14db293b 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 27bc80e51..6d0577487 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 dd4364774..d725e43d2 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); From b44b624ca503adfccc92365275f23c94e82e96f2 Mon Sep 17 00:00:00 2001 From: David Adam Date: Sun, 2 Feb 2014 21:34:40 +0800 Subject: [PATCH 07/14] Documented Alt-Right to accept a single word of an autosuggestion See https://github.com/fish-shell/fish-shell/issues/1262 --- doc_src/index.hdr.in | 7 ++++--- doc_src/tutorial.hdr | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc_src/index.hdr.in b/doc_src/index.hdr.in index f82f309aa..e1eab45ef 100644 --- a/doc_src/index.hdr.in +++ b/doc_src/index.hdr.in @@ -387,8 +387,9 @@ cursor, in a muted gray color (which can be changed with the fish_color_autosuggestion variable). To accept the autosuggestion (replacing the command line contents), -hit right arrow or Control-F. If the autosuggestion is not what you want, -just ignore it: it won't execute unless you accept it. +press right arrow or Control-F. To accept the first suggested word, press +Alt-Right. If the autosuggestion is not what you want, just ignore it: it won't +execute unless you accept it. Autosuggestions are a powerful way to quickly summon frequently entered commands, by typing the first few characters. They are also an efficient technique for navigating @@ -1123,7 +1124,7 @@ Here are some of the commands available in the editor: - Home or Ctrl-A moves the cursor to the beginning of the line. - End or Ctrl-E moves to the end of line. If the cursor is already at the end of the line, and an autosuggestion is available, End or Ctrl-E accepts the autosuggestion. - Left (or Ctrl-B) and Right (or Ctrl-F) move the cursor left or right by one character. If the cursor is already at the end of the line, and an autosuggestion is available, the Right key and the Ctrl-F combination accept the suggestion. -- Alt-Left and Alt-Right move the cursor one word left or right, or moves forward/backward in the directory history if the command line is empty. +- Alt-Left and Alt-Right move the cursor one word left or right, or moves forward/backward in the directory history if the command line is empty. If the cursor is already at the end of the line, and an autosuggestion is available, Alt-Right accept the first word in the suggestion. - Up and Down search the command history for the previous/next command containing the string that was specified on the commandline before the search was started. If the commandline was empty when the search started, all commands match. See the history section for more information on history searching. - Alt-Up and Alt-Down search the command history for the previous/next token containing the token under the cursor before the search was started. If the commandline was not on a token when the search started, all tokens match. See the history section for more information on history searching. - Delete and Backspace removes one character forwards or backwards respectively. diff --git a/doc_src/tutorial.hdr b/doc_src/tutorial.hdr index d5cd69482..e026bb3c2 100644 --- a/doc_src/tutorial.hdr +++ b/doc_src/tutorial.hdr @@ -349,7 +349,7 @@ And history too. Type a command once, and you can re-summon it by just typing a > rsync -avze ssh . myname@somelonghost.com:/some/long/path/doo/dee/doo/dee/doo -To accept the autosuggestion, hit right arrow or Control-F. If the autosuggestion is not what you want, just ignore it. +To accept the autosuggestion, hit right arrow or Control-F. To accept a single word of the autosuggestion, hit Alt+right arrow. If the autosuggestion is not what you want, just ignore it.

Tab Completions

From 397249a8d5a939d044da8ecfbb1654d48ce5a153 Mon Sep 17 00:00:00 2001 From: David Adam Date: Mon, 4 Aug 2014 13:34:26 +0800 Subject: [PATCH 08/14] 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. --- share/tools/web_config/index.html | 22 ++++----- share/tools/web_config/webconfig.py | 71 ++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/share/tools/web_config/index.html b/share/tools/web_config/index.html index 22cd4705b..90df11435 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 1b9250b17..2a103eb63 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) From 78e2b7cc0897de3eb2a8cdc0f5efe49a34402f9d Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Mon, 11 Aug 2014 17:50:56 -0700 Subject: [PATCH 09/14] webconfig: Use a constant-time token comparison This prevents a linear-time attack to recover the auth token. --- share/tools/web_config/webconfig.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index 2a103eb63..452f7716e 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) From 3e2d68a059d559b59cac1f513b31f4d710dab806 Mon Sep 17 00:00:00 2001 From: Andy Lutomirski Date: Mon, 11 Aug 2014 17:51:27 -0700 Subject: [PATCH 10/14] webconfig: fixes for token security * Use 16-byte tokens * Use os.urandom (random.getrandbits shouldn't be used for security) * Convert to hex correctly --- share/tools/web_config/webconfig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index 452f7716e..43d2ced0d 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, random, string +import re, socket, os, sys, cgi, select, time, glob, random, string, binascii try: import json except ImportError: @@ -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 = binascii.b2a_hex(os.urandom(16)) # Try to find a suitable port PORT = 8000 From fd70ae0b61a138f900322beeaa89cf05986950ea Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 3 Sep 2014 23:58:36 -0700 Subject: [PATCH 11/14] Kill fishd after installing fish for OS X, to pick up fishd changes --- build_tools/osx_package_scripts/postinstall | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_tools/osx_package_scripts/postinstall b/build_tools/osx_package_scripts/postinstall index 544fd1de1..8a4ddbcf3 100755 --- a/build_tools/osx_package_scripts/postinstall +++ b/build_tools/osx_package_scripts/postinstall @@ -1,3 +1,5 @@ #!/bin/sh -x ./add-shell /usr/local/bin/fish > /tmp/fish_postinstall_output.log +killall fishd || true + From 9c78295a9a4eb243fc8876a89ee7edd11999293c Mon Sep 17 00:00:00 2001 From: David Adam Date: Mon, 28 Apr 2014 23:37:02 +0800 Subject: [PATCH 12/14] avoid symlink attacks in __fish_print_packages * use $XDG_CACHE_HOME for __fish_print_packages completion caches Fix for CVE-2014-3219. Closes #1440. --- share/functions/__fish_print_packages.fish | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/share/functions/__fish_print_packages.fish b/share/functions/__fish_print_packages.fish index decf410df..960c03c5b 100644 --- a/share/functions/__fish_print_packages.fish +++ b/share/functions/__fish_print_packages.fish @@ -12,6 +12,12 @@ function __fish_print_packages #Get the word 'Package' in the current language set -l package (_ Package) + # Set up cache directory + if test -z "$XDG_CACHE_HOME" + set XDG_CACHE_HOME $HOME/.cache + end + mkdir -m 700 -p $XDG_CACHE_HOME + if type -f apt-cache >/dev/null # Do not generate the cache as apparently sometimes this is slow. # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=547550 @@ -35,7 +41,7 @@ function __fish_print_packages # If the cache is less than six hours old, we do not recalculate it - set cache_file /tmp/.yum-cache.$USER + set cache_file $XDG_CACHE_HOME/.yum-cache.$USER if test -f $cache_file cat $cache_file set age (math (date +%s) - (stat -c '%Y' $cache_file)) @@ -56,7 +62,7 @@ function __fish_print_packages # If the cache is less than five minutes old, we do not recalculate it - set cache_file /tmp/.rpm-cache.$USER + set cache_file $XDG_CACHE_HOME/.rpm-cache.$USER if test -f $cache_file cat $cache_file set age (math (date +%s) - (stat -c '%Y' $cache_file)) From 482e615fe0dbf6a5dd261451201d8217fb123564 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 5 Sep 2014 08:46:43 -0700 Subject: [PATCH 13/14] Bump osx/config.h version number to 2.1.1 --- fish.xcodeproj/project.pbxproj | 6 +++--- osx/config.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index 9f1ad217b..8ce32a136 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -1190,7 +1190,7 @@ "DATADIR=L\\\"/usr/local/share\\\"", "SYSCONFDIR=L\\\"/usr/local/etc\\\"", "BINDIR=L\\\"/usr/local/bin\\\"", - "FISH_BUILD_VERSION=\\\"2.1.0\\\"", + "FISH_BUILD_VERSION=\\\"2.1.1\\\"", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; @@ -1342,7 +1342,7 @@ "DATADIR=L\\\"/usr/local/share\\\"", "SYSCONFDIR=L\\\"/usr/local/etc\\\"", "BINDIR=L\\\"/usr/local/bin\\\"", - "FISH_BUILD_VERSION=\\\"2.1.0\\\"", + "FISH_BUILD_VERSION=\\\"2.1.1\\\"", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1370,7 +1370,7 @@ "DATADIR=L\\\"/usr/local/share\\\"", "SYSCONFDIR=L\\\"/usr/local/etc\\\"", "BINDIR=L\\\"/usr/local/bin\\\"", - "FISH_BUILD_VERSION=\\\"2.1.0\\\"", + "FISH_BUILD_VERSION=\\\"2.1.1\\\"", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; diff --git a/osx/config.h b/osx/config.h index 4968a78b2..5b74f106e 100644 --- a/osx/config.h +++ b/osx/config.h @@ -183,7 +183,7 @@ #define PACKAGE_NAME "fish" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "fish 2.1.0" +#define PACKAGE_STRING "fish 2.1.1" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "fish" @@ -192,7 +192,7 @@ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "2.1.0" +#define PACKAGE_VERSION "2.1.1" /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 From 7f4908b0db5f14566f9881c9ac01154eaba78ee3 Mon Sep 17 00:00:00 2001 From: qjcg Date: Fri, 12 Sep 2014 11:36:28 -0400 Subject: [PATCH 14/14] Fix webconfig URL generation for python3 Closes #1677. Signed-off-by: David Adam --- share/tools/web_config/webconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index 43d2ced0d..302584cd0 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 = binascii.b2a_hex(os.urandom(16)) +authkey = binascii.b2a_hex(os.urandom(16)).decode('ascii') # Try to find a suitable port PORT = 8000