From af92181055d7d64dfc0bc9d5a13c8b98af3196be Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 25 May 2022 10:09:53 +0200 Subject: [PATCH] fopen: add Curl_fopen() for better overwriting of files Bug: https://curl.se/docs/CVE-2022-32207.html CVE-2022-32207 Reported-by: Harry Sintonen Closes #9050 Upstream-Status: Backport [https://github.com/curl/curl/commit/20f9dd6bae50b] Signed-off-by: Robert Joslyn --- CMakeLists.txt | 1 + configure.ac | 1 + lib/Makefile.inc | 4 +- lib/cookie.c | 19 ++----- lib/curl_config.h.cmake | 3 ++ lib/fopen.c | 113 ++++++++++++++++++++++++++++++++++++++++ lib/fopen.h | 30 +++++++++++ 7 files changed, 155 insertions(+), 16 deletions(-) create mode 100644 lib/fopen.c create mode 100644 lib/fopen.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 73b053b..cc587b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -869,6 +869,7 @@ elseif(HAVE_LIBSOCKET) set(CMAKE_REQUIRED_LIBRARIES socket) endif() +check_symbol_exists(fchmod "${CURL_INCLUDES}" HAVE_FCHMOD) check_symbol_exists(basename "${CURL_INCLUDES}" HAVE_BASENAME) check_symbol_exists(socket "${CURL_INCLUDES}" HAVE_SOCKET) check_symbol_exists(select "${CURL_INCLUDES}" HAVE_SELECT) diff --git a/configure.ac b/configure.ac index d090622..7071077 100755 --- a/configure.ac +++ b/configure.ac @@ -4059,6 +4059,7 @@ AC_CHECK_DECLS([getpwuid_r], [], [AC_DEFINE(HAVE_DECL_GETPWUID_R_MISSING, 1, "Se AC_CHECK_FUNCS([fnmatch \ + fchmod \ geteuid \ getpass_r \ getppid \ diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 46ded90..79307d8 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -63,7 +63,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ curl_multibyte.c hostcheck.c conncache.c dotdot.c \ x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \ mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \ - doh.c urlapi.c curl_get_line.c altsvc.c socketpair.c rename.c + doh.c urlapi.c curl_get_line.c altsvc.c socketpair.c rename.c fopen.c LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ @@ -84,7 +84,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \ curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \ curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h \ - curl_get_line.h altsvc.h quic.h socketpair.h rename.h + curl_get_line.h altsvc.h quic.h socketpair.h rename.h fopen.h LIB_RCFILES = libcurl.rc diff --git a/lib/cookie.c b/lib/cookie.c index 68054e1..a9ad20a 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -97,8 +97,8 @@ Example set of cookies: #include "curl_memrchr.h" #include "inet_pton.h" #include "parsedate.h" -#include "rand.h" #include "rename.h" +#include "fopen.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -1524,18 +1524,9 @@ static int cookie_output(struct Curl_easy *data, use_stdout = TRUE; } else { - unsigned char randsuffix[9]; - - if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix))) - return 2; - - tempstore = aprintf("%s.%s.tmp", filename, randsuffix); - if(!tempstore) - return 1; - - out = fopen(tempstore, FOPEN_WRITETEXT); - if(!out) - goto error; + error = Curl_fopen(data, filename, &out, &tempstore); + if(error) + goto error; } fputs("# Netscape HTTP Cookie File\n" @@ -1581,7 +1572,7 @@ static int cookie_output(struct Curl_easy *data, if(!use_stdout) { fclose(out); out = NULL; - if(Curl_rename(tempstore, filename)) { + if(tempstore && Curl_rename(tempstore, filename)) { unlink(tempstore); goto error; } diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index 98cdf51..fe43751 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -124,6 +124,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ASSERT_H 1 +/* Define to 1 if you have the `fchmod' function. */ +#cmakedefine HAVE_FCHMOD 1 + /* Define to 1 if you have the `basename' function. */ #cmakedefine HAVE_BASENAME 1 diff --git a/lib/fopen.c b/lib/fopen.c new file mode 100644 index 0000000..ad3691b --- /dev/null +++ b/lib/fopen.c @@ -0,0 +1,113 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC) || \ + !defined(CURL_DISABLE_HSTS) + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include "urldata.h" +#include "rand.h" +#include "fopen.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* + * Curl_fopen() opens a file for writing with a temp name, to be renamed + * to the final name when completed. If there is an existing file using this + * name at the time of the open, this function will clone the mode from that + * file. if 'tempname' is non-NULL, it needs a rename after the file is + * written. + */ +CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, + FILE **fh, char **tempname) +{ + CURLcode result = CURLE_WRITE_ERROR; + unsigned char randsuffix[9]; + char *tempstore = NULL; + struct_stat sb; + int fd = -1; + *tempname = NULL; + + if(stat(filename, &sb) == -1 || !S_ISREG(sb.st_mode)) { + /* a non-regular file, fallback to direct fopen() */ + *fh = fopen(filename, FOPEN_WRITETEXT); + if(*fh) + return CURLE_OK; + goto fail; + } + + result = Curl_rand_hex(data, randsuffix, sizeof(randsuffix)); + if(result) + goto fail; + + tempstore = aprintf("%s.%s.tmp", filename, randsuffix); + if(!tempstore) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + result = CURLE_WRITE_ERROR; + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600); + if(fd == -1) + goto fail; + +#ifdef HAVE_FCHMOD + { + struct_stat nsb; + if((fstat(fd, &nsb) != -1) && + (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { + /* if the user and group are the same, clone the original mode */ + if(fchmod(fd, sb.st_mode) == -1) + goto fail; + } + } +#endif + + *fh = fdopen(fd, FOPEN_WRITETEXT); + if(!*fh) + goto fail; + + *tempname = tempstore; + return CURLE_OK; + +fail: + if(fd != -1) { + close(fd); + unlink(tempstore); + } + + free(tempstore); + + *tempname = NULL; + return result; +} + +#endif /* ! disabled */ diff --git a/lib/fopen.h b/lib/fopen.h new file mode 100644 index 0000000..289e55f --- /dev/null +++ b/lib/fopen.h @@ -0,0 +1,30 @@ +#ifndef HEADER_CURL_FOPEN_H +#define HEADER_CURL_FOPEN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, + FILE **fh, char **tempname); + +#endif