From 1c5f0e2905a1f689a2984cc3b43a05f984aede1a Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 20 Sep 2022 15:08:59 +0900 Subject: trust: make filesystem path construction flexible To handle pathnames longer than the fixed length (previously 256), this adds a set of internal API functions around the gnutls_pathbuf_st struct, which enables to safely and efficiently construct pathnames. The new API initially uses the statically allocated buffer and starts allocating memory on heap only after the limit has reached. Signed-off-by: Daiki Ueno --- .gitignore | 7 ++- bootstrap.conf | 2 +- lib/Makefile.am | 2 +- lib/libgnutls.map | 5 ++ lib/pathbuf.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/pathbuf.h | 49 ++++++++++++++++ lib/system.h | 6 +- lib/system/certs.c | 43 ++++++++------ lib/x509/verify-high2.c | 126 +++++++++++++++++++++++++++-------------- tests/Makefile.am | 6 +- tests/pathbuf.c | 66 ++++++++++++++++++++++ 11 files changed, 390 insertions(+), 68 deletions(-) create mode 100644 lib/pathbuf.c create mode 100644 lib/pathbuf.h create mode 100644 tests/pathbuf.c diff --git a/.gitignore b/.gitignore index 288efb9c59..597530a6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -414,6 +414,7 @@ tests/gnutls-strcodes tests/gnutls_ext_raw_parse tests/gnutls_ext_raw_parse_dtls tests/gnutls_hmac_fast +tests/gnutls_ktls tests/gnutls_ocsp_resp_list_import2 tests/gnutls_record_overhead tests/gnutls_session_set_id @@ -452,6 +453,7 @@ tests/key-usage-rsa tests/keygen tests/keylog-env tests/keylog-func +tests/ktls_keyupdate tests/libpkcs11mock1.la tests/libpkcs11mock2.la tests/libutils.la @@ -545,6 +547,7 @@ tests/openpgp-keyring tests/openpgpself tests/openssl tests/parse_ca +tests/pathbuf tests/pathlen/Makefile tests/pathlen/Makefile.in tests/pcert-list @@ -625,6 +628,7 @@ tests/random-art tests/rawpk-api tests/record-pad tests/record-retvals +tests/record-sendfile tests/record-sizes tests/record-sizes-range tests/record-timeouts @@ -918,9 +922,6 @@ tests/x509sign-verify-rsa tests/x509sign-verify2 tests/x509signself tests/xts-key-check -tests/gnutls_ktls -tests/record-sendfile -tests/ktls_keyupdate *.tmp tmp-* *.trs diff --git a/bootstrap.conf b/bootstrap.conf index ad8d7cd51e..c4d1fe06b2 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -27,7 +27,7 @@ required_submodules="tests/suite/tls-fuzzer/python-ecdsa tests/suite/tls-fuzzer/ # Those modules are common to lib/ and src/. common_modules=" -alloca attribute byteswap c-ctype c-strcase canonicalize-lgpl explicit_bzero fopen-gnu func getline gettext-h gettimeofday hash hash-pjw-bare arpa_inet inet_ntop inet_pton intprops linkedhash-list lock memmem-simple minmax netdb netinet_in read-file secure_getenv setsockopt snprintf stdint stpcpy strcase strdup-posix strndup strtok_r strverscmp sys_socket sys_stat sys_types threadlib time_r tls unistd valgrind-tests vasprintf verify vsnprintf xalloc-oversized +alloca attribute byteswap c-ctype c-strcase canonicalize-lgpl explicit_bzero fopen-gnu func getline gettext-h gettimeofday hash hash-pjw-bare arpa_inet inet_ntop inet_pton intprops linkedhash-list lock memmem-simple minmax netdb netinet_in pathmax read-file secure_getenv setsockopt snprintf stdint stpcpy strcase strdup-posix strndup strtok_r strverscmp sys_socket sys_stat sys_types threadlib time_r tls unistd valgrind-tests vasprintf verify vsnprintf xalloc-oversized " gnulib_modules=" $common_modules dirname-lgpl extensions gendocs havelib ldd lib-msvc-compat lib-symbol-versions maintainer-makefile manywarnings pmccabe2html warnings diff --git a/lib/Makefile.am b/lib/Makefile.am index fc83a37333..6d4e8d225a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -83,7 +83,7 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls cert-session.c handshake-checks.c dtls-sw.c dh-primes.c openpgp_compat.c \ crypto-selftests.c crypto-selftests-pk.c secrets.c extv.c extv.h \ hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c cert-cred-rawpk.c \ - iov.c iov.h system/ktls.c system/ktls.h + iov.c iov.h system/ktls.c system/ktls.h pathbuf.c pathbuf.h if ENABLE_GOST COBJECTS += vko.c diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 31cbb90489..72e425c3a0 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1514,4 +1514,9 @@ GNUTLS_PRIVATE_3_4 { _gnutls_crypto_register_cipher; # needed by tests/tls12-rehandshake-cert-ticket _gnutls_session_ticket_disable_server; + # needed by tests/pathbuf + _gnutls_pathbuf_init; + _gnutls_pathbuf_append; + _gnutls_pathbuf_truncate; + _gnutls_pathbuf_deinit; } GNUTLS_3_4; diff --git a/lib/pathbuf.c b/lib/pathbuf.c new file mode 100644 index 0000000000..a5d6316fcd --- /dev/null +++ b/lib/pathbuf.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2010-2022 Free Software Foundation, Inc. + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +#include "config.h" + +#include "pathbuf.h" +#include "gnutls_int.h" +#include +#include "intprops.h" + +static int +pathbuf_reserve(struct gnutls_pathbuf_st *buffer, size_t to_add) +{ + size_t len; + char *ptr; + + len = buffer->len; + + if (INT_ADD_OVERFLOW(len, to_add)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + len += to_add; + + /* NUL terminator. */ + if (INT_ADD_OVERFLOW(len, 1)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + len++; + + if (len <= buffer->cap) { + return 0; + } + + if (buffer->ptr == buffer->base) { + ptr = gnutls_strdup(buffer->ptr); + if (!ptr) { + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + buffer->ptr = ptr; + } + + ptr = gnutls_realloc(buffer->ptr, len); + if (!ptr) { + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + + buffer->ptr = ptr; + buffer->cap = len; + + return 0; +} + +int +_gnutls_pathbuf_init(struct gnutls_pathbuf_st *buffer, const char *base) +{ + size_t len; + int ret; + + memset(buffer, 0, sizeof(*buffer)); + buffer->cap = sizeof(buffer->base); + buffer->ptr = buffer->base; + + len = strlen(base); + + ret = pathbuf_reserve(buffer, len); + if (ret < 0) { + return ret; + } + + strcpy(buffer->ptr, base); + buffer->len = len; + + return 0; +} + +int +_gnutls_pathbuf_append(struct gnutls_pathbuf_st *buffer, const char *component) +{ + size_t len; + char *p; + int ret; + + len = strlen(component); + + /* Path separator. */ + if (INT_ADD_OVERFLOW(len, 1)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + len++; + + ret = pathbuf_reserve(buffer, len); + if (ret < 0) { + return ret; + } + + p = stpcpy(&buffer->ptr[buffer->len], "/"); + strcpy(p, component); + + /* Overflow is already checked in the call to pathbuf_reserve + * above. + */ + buffer->len += len; + + return 0; +} + +int +_gnutls_pathbuf_truncate(struct gnutls_pathbuf_st *buffer, size_t len) +{ + if (len > buffer->len) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + buffer->len = len; + buffer->ptr[len] = '\0'; + return 0; +} + +void +_gnutls_pathbuf_deinit(struct gnutls_pathbuf_st *buffer) +{ + if (buffer->ptr != buffer->base) { + gnutls_free(buffer->ptr); + } + memset(buffer, 0, sizeof(*buffer)); +} + diff --git a/lib/pathbuf.h b/lib/pathbuf.h new file mode 100644 index 0000000000..2739e4dd29 --- /dev/null +++ b/lib/pathbuf.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010-2022 Free Software Foundation, Inc. + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +#ifndef GNUTLS_LIB_PATHBUF_H +#define GNUTLS_LIB_PATHBUF_H + +#include "pathmax.h" +#define GNUTLS_PATH_MAX PATH_MAX + +struct gnutls_pathbuf_st { + char base[GNUTLS_PATH_MAX + 1]; + char *ptr; /* API */ + size_t len; /* API: NOT including NUL */ + size_t cap; /* including NUL */ +}; + +/* Initialize BUFFER with the content BASE. */ +int _gnutls_pathbuf_init(struct gnutls_pathbuf_st *buffer, const char *base); + +/* Append COMPONENT to BUFFER, separated with a "/". */ +int _gnutls_pathbuf_append(struct gnutls_pathbuf_st *buffer, const char *component); + +/* Truncate the length of BUFFER to LEN. */ +int _gnutls_pathbuf_truncate(struct gnutls_pathbuf_st *buffer, size_t len); + +/* Deinitialize BUFFER. */ +void _gnutls_pathbuf_deinit(struct gnutls_pathbuf_st *buffer); + +#endif /* GNUTLS_LIB_PATHBUF_H */ diff --git a/lib/system.h b/lib/system.h index e15c8cd33d..13cbf1a9fe 100644 --- a/lib/system.h +++ b/lib/system.h @@ -44,11 +44,7 @@ extern CertEnumCRLsInStoreFunc pCertEnumCRLsInStore; #include /* for writev */ #endif -#ifdef _POSIX_PATH_MAX -# define GNUTLS_PATH_MAX _POSIX_PATH_MAX -#else -# define GNUTLS_PATH_MAX 256 -#endif +#include "pathbuf.h" int system_errno(gnutls_transport_ptr_t); diff --git a/lib/system/certs.c b/lib/system/certs.c index c06b82354b..e155bd9389 100644 --- a/lib/system/certs.c +++ b/lib/system/certs.c @@ -217,31 +217,42 @@ int add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, # if defined(ANDROID) || defined(__ANDROID__) # define DEFAULT_TRUST_STORE_DIR "/system/etc/security/cacerts/" +# define DEFAULT_REVOCATION_DIR "/data/misc/keychain/cacerts-removed" + static int load_revoked_certs(gnutls_x509_trust_list_t list, unsigned type) { DIR *dirp; struct dirent *d; int ret; int r = 0; - char path[GNUTLS_PATH_MAX]; + struct gnutls_pathbuf_st pathbuf; - dirp = opendir("/data/misc/keychain/cacerts-removed/"); + dirp = opendir(DEFAULT_REVOCATION_DIR); if (dirp != NULL) { - do { - d = readdir(dirp); - if (d != NULL && d->d_type == DT_REG) { - snprintf(path, sizeof(path), - "/data/misc/keychain/cacerts-removed/%s", - d->d_name); - - ret = - gnutls_x509_trust_list_remove_trust_file - (list, path, type); - if (ret >= 0) - r += ret; - } + size_t base_len; + + ret = _gnutls_pathbuf_init(&pathbuf, DEFAULT_REVOCATION_DIR); + if (ret < 0) { + return 0; + } + + base_len = pathbuf.len; + while ((d = readdir(dirp)) != NULL) { + if (d->d_type != DT_REG) { + continue; + } + ret = _gnutls_pathbuf_append(&pathbuf, d->d_name); + if (ret < 0) { + continue; + } + ret = gnutls_x509_trust_list_remove_trust_file + (list, pathbuf.ptr, type); + if (ret >= 0) { + r += ret; + } + (void)_gnutls_pathbuf_truncate(&pathbuf, base_len); } - while (d != NULL); + _gnutls_pathbuf_deinit(&pathbuf); closedir(dirp); } diff --git a/lib/x509/verify-high2.c b/lib/x509/verify-high2.c index eaaaa8b897..e69b1567c4 100644 --- a/lib/x509/verify-high2.c +++ b/lib/x509/verify-high2.c @@ -393,7 +393,7 @@ int load_dir_certs(const char *dirname, { int ret; int r = 0; - char path[GNUTLS_PATH_MAX]; + struct gnutls_pathbuf_st pathbuf; #if !defined(_WIN32) || !defined(_UNICODE) DIR *dirp; @@ -401,29 +401,44 @@ int load_dir_certs(const char *dirname, dirp = opendir(dirname); if (dirp != NULL) { + size_t base_len; + + ret = _gnutls_pathbuf_init(&pathbuf, dirname); + if (ret < 0) { + return r; + } + + base_len = pathbuf.len; while ((d = readdir(dirp)) != NULL) { #ifdef _DIRENT_HAVE_D_TYPE - if (d->d_type == DT_REG || d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) + switch (d->d_type) { + case DT_REG: + case DT_LNK: + case DT_UNKNOWN: + break; + default: + continue; + } #endif - { - snprintf(path, sizeof(path), "%s/%s", - dirname, d->d_name); - - if (crl != 0) { - ret = - gnutls_x509_trust_list_add_trust_file - (list, NULL, path, type, tl_flags, - tl_vflags); - } else { - ret = - gnutls_x509_trust_list_add_trust_file - (list, path, NULL, type, tl_flags, - tl_vflags); - } - if (ret >= 0) - r += ret; + ret = _gnutls_pathbuf_append(&pathbuf, d->d_name); + if (ret < 0) { + continue; + } + if (crl != 0) { + ret = gnutls_x509_trust_list_add_trust_file + (list, NULL, pathbuf.ptr, type, tl_flags, + tl_vflags); + } else { + ret = gnutls_x509_trust_list_add_trust_file + (list, pathbuf.ptr, NULL, type, tl_flags, + tl_vflags); } + if (ret >= 0) { + r += ret; + } + (void)_gnutls_pathbuf_truncate(&pathbuf, base_len); } + _gnutls_pathbuf_deinit(&pathbuf); closedir(dirp); } #else /* _WIN32 */ @@ -432,41 +447,70 @@ int load_dir_certs(const char *dirname, struct _tdirent *d; gnutls_datum_t utf16 = {NULL, 0}; +#undef UCS2_ENDIAN #ifdef WORDS_BIGENDIAN - r = _gnutls_utf8_to_ucs2(dirname, strlen(dirname), &utf16, 1); +#define UCS2_ENDIAN 1 #else - r = _gnutls_utf8_to_ucs2(dirname, strlen(dirname), &utf16, 0); +#define UCS2_ENDIAN 0 #endif - if (r < 0) - return gnutls_assert_val(r); + + ret = _gnutls_utf8_to_ucs2(dirname, strlen(dirname), &utf16, UCS2_ENDIAN); + if (ret < 0) { + return gnutls_assert_val(ret); + } dirp = _topendir((_TCHAR*)utf16.data); gnutls_free(utf16.data); if (dirp != NULL) { + size_t base_len; + + ret = _gnutls_pathbuf_init(&pathbuf, dirname); + if (ret < 0) { + return r; + } + + base_len = pathbuf.len; while ((d = _treaddir(dirp)) != NULL) { + gnutls_datum_t utf8 = {NULL, 0}; #ifdef _DIRENT_HAVE_D_TYPE - if (d->d_type == DT_REG || d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) + switch (d->d_type) { + case DT_REG: + case DT_LNK: + case DT_UNKNOWN: + break; + default: + continue; + } #endif - { - snprintf(path, sizeof(path), "%s/%ls", - dirname, d->d_name); - - if (crl != 0) { - ret = - gnutls_x509_trust_list_add_trust_file - (list, NULL, path, type, tl_flags, - tl_vflags); - } else { - ret = - gnutls_x509_trust_list_add_trust_file - (list, path, NULL, type, tl_flags, - tl_vflags); - } - if (ret >= 0) - r += ret; + ret = _gnutls_ucs2_to_utf8(d->d_name, + d->d_namlen * sizeof(d->d_name[0]), + &utf8, + UCS2_ENDIAN); + if (ret < 0) { + continue; + } + ret = _gnutls_pathbuf_append(&pathbuf, utf8.data); + gnutls_free(utf8.data); + if (ret < 0) { + continue; + } + + if (crl != 0) { + ret = gnutls_x509_trust_list_add_trust_file + (list, NULL, pathbuf.ptr, type, tl_flags, + tl_vflags); + } else { + ret = gnutls_x509_trust_list_add_trust_file + (list, pathbuf.ptr, NULL, type, tl_flags, + tl_vflags); } + if (ret >= 0) + r += ret; + (void)_gnutls_pathbuf_truncate(&pathbuf, base_len); } + _gnutls_pathbuf_deinit(&pathbuf); _tclosedir(dirp); } +#undef UCS2_ENDIAN #endif /* _WIN32 */ return r; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 573e911a0b..2872cb1aa5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -234,7 +234,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei set_x509_ocsp_multi_cli kdf-api keylog-func handshake-write \ x509cert-dntypes id-on-xmppAddr tls13-compat-mode ciphersuite-name \ x509-upnconstraint xts-key-check cipher-padding pkcs7-verify-double-free \ - fips-rsa-sizes tls12-rehandshake-ticket + fips-rsa-sizes tls12-rehandshake-ticket pathbuf ctests += tls-channel-binding @@ -480,6 +480,10 @@ buffer_CPPFLAGS = $(AM_CPPFLAGS) \ -I$(top_srcdir)/gl \ -I$(top_builddir)/gl +pathbuf_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_srcdir)/gl \ + -I$(top_builddir)/gl + if ENABLE_PKCS11 if !WINDOWS ctests += tls13/post-handshake-with-cert-pkcs11 pkcs11/tls-neg-pkcs11-no-key \ diff --git a/tests/pathbuf.c b/tests/pathbuf.c new file mode 100644 index 0000000000..f308ac090a --- /dev/null +++ b/tests/pathbuf.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + * + */ + +#include + +#include "../lib/pathbuf.h" +#include "utils.h" +#include +#include + +static char long_path[GNUTLS_PATH_MAX + 2]; + +void +doit(void) +{ + struct gnutls_pathbuf_st pathbuf; + int i; + int ret; + + ret = _gnutls_pathbuf_init(&pathbuf, "./x509certs"); + assert(ret == 0); + assert(strcmp(pathbuf.ptr, "./x509certs") == 0); + assert(pathbuf.len == sizeof("./x509certs") - 1); + + ret = _gnutls_pathbuf_append(&pathbuf, "cert.pem"); + assert(ret == 0); + assert(strcmp(pathbuf.ptr, "./x509certs/cert.pem") == 0); + assert(pathbuf.len == sizeof("./x509certs/cert.pem") - 1); + _gnutls_pathbuf_deinit(&pathbuf); + + for (i = -1; i <= 1; i++) { + memset(long_path, 'a', GNUTLS_PATH_MAX + i); + long_path[GNUTLS_PATH_MAX + i] = '\0'; + + ret = _gnutls_pathbuf_init(&pathbuf, long_path); + assert(ret == 0); + assert(strcmp(pathbuf.ptr, long_path) == 0); + assert(pathbuf.len == (size_t)GNUTLS_PATH_MAX + i); + + ret = _gnutls_pathbuf_append(&pathbuf, "cert.pem"); + assert(ret == 0); + assert(memcmp(pathbuf.ptr, long_path, GNUTLS_PATH_MAX + i) == 0); + assert(strcmp(&pathbuf.ptr[GNUTLS_PATH_MAX + i], "/cert.pem") == 0); + assert(pathbuf.len == GNUTLS_PATH_MAX + i + sizeof("/cert.pem") - 1); + _gnutls_pathbuf_deinit(&pathbuf); + } +} -- cgit v1.2.1