From 872c83f43810e8cc786e0ec2a7c457cbe23a9ceb Mon Sep 17 00:00:00 2001 From: eabdullin Date: Wed, 22 May 2024 13:41:54 +0000 Subject: [PATCH] import UBI pam-1.3.1-33.el8 --- .../pam-1.3.1-access-handle-hostnames.patch | 158 ++++++++++++++++ .../pam-1.3.1-faillock-create-tallydir.patch | 13 ++ SOURCES/pam-1.3.1-namespace-protect-dir.patch | 58 ++++++ SOURCES/pam-1.3.1-unix-default-rounds.patch | 12 ++ SOURCES/pam-1.3.1-unix-enable-bcrypt.patch | 174 ++++++++++++++++++ SPECS/pam.spec | 30 ++- 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 SOURCES/pam-1.3.1-access-handle-hostnames.patch create mode 100644 SOURCES/pam-1.3.1-faillock-create-tallydir.patch create mode 100644 SOURCES/pam-1.3.1-namespace-protect-dir.patch create mode 100644 SOURCES/pam-1.3.1-unix-default-rounds.patch create mode 100644 SOURCES/pam-1.3.1-unix-enable-bcrypt.patch diff --git a/SOURCES/pam-1.3.1-access-handle-hostnames.patch b/SOURCES/pam-1.3.1-access-handle-hostnames.patch new file mode 100644 index 0000000..b73ba8a --- /dev/null +++ b/SOURCES/pam-1.3.1-access-handle-hostnames.patch @@ -0,0 +1,158 @@ +diff -up Linux-PAM-1.3.1/modules/pam_access/pam_access.c.access-handle-hostnames Linux-PAM-1.3.1/modules/pam_access/pam_access.c +--- Linux-PAM-1.3.1/modules/pam_access/pam_access.c.access-handle-hostnames 2024-01-19 16:45:18.319862531 +0100 ++++ Linux-PAM-1.3.1/modules/pam_access/pam_access.c 2024-01-19 16:50:34.239545948 +0100 +@@ -683,7 +683,7 @@ string_match (pam_handle_t *pamh, const + /* + * If the token has the magic value "ALL" the match always succeeds. + * Otherwise, return YES if the token fully matches the string. +- * "NONE" token matches NULL string. ++ * "NONE" token matches NULL string. + */ + + if (strcasecmp(tok, "ALL") == 0) { /* all: always matches */ +@@ -701,7 +701,8 @@ string_match (pam_handle_t *pamh, const + + /* network_netmask_match - match a string against one token + * where string is a hostname or ip (v4,v6) address and tok +- * represents either a single ip (v4,v6) address or a network/netmask ++ * represents either a hostname, a single ip (v4,v6) address ++ * or a network/netmask + */ + static int + network_netmask_match (pam_handle_t *pamh, +@@ -710,10 +711,12 @@ network_netmask_match (pam_handle_t *pam + char *netmask_ptr; + char netmask_string[MAXHOSTNAMELEN + 1]; + int addr_type; ++ struct addrinfo *ai = NULL; + + if (item->debug) +- pam_syslog (pamh, LOG_DEBUG, ++ pam_syslog (pamh, LOG_DEBUG, + "network_netmask_match: tok=%s, item=%s", tok, string); ++ + /* OK, check if tok is of type addr/mask */ + if ((netmask_ptr = strchr(tok, '/')) != NULL) + { +@@ -745,52 +748,109 @@ network_netmask_match (pam_handle_t *pam + netmask_ptr = number_to_netmask(netmask, addr_type, + netmask_string, MAXHOSTNAMELEN); + } +- } ++ ++ /* ++ * Construct an addrinfo list from the IP address. ++ * This should not fail as the input is a correct IP address... ++ */ ++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0) ++ { ++ return NO; ++ } ++ } + else +- /* NO, then check if it is only an addr */ +- if (isipaddr(tok, NULL, NULL) != YES) ++ { ++ /* ++ * It is either an IP address or a hostname. ++ * Let getaddrinfo sort everything out ++ */ ++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0) + { ++ if (item->debug) ++ pam_syslog(pamh, LOG_DEBUG, "cannot resolve hostname \"%s\"", tok); ++ + return NO; + } ++ netmask_ptr = NULL; ++ } + + if (isipaddr(string, NULL, NULL) != YES) + { +- /* Assume network/netmask with a name of a host. */ + struct addrinfo hint; + ++ /* Assume network/netmask with a name of a host. */ + memset (&hint, '\0', sizeof (hint)); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_UNSPEC; + + if (item->gai_rv != 0) ++ { ++ freeaddrinfo(ai); + return NO; ++ } + else if (!item->res && + (item->gai_rv = getaddrinfo (string, NULL, &hint, &item->res)) != 0) ++ { ++ freeaddrinfo(ai); + return NO; ++ } + else + { + struct addrinfo *runp = item->res; ++ struct addrinfo *runp1; + + while (runp != NULL) + { + char buf[INET6_ADDRSTRLEN]; + +- inet_ntop (runp->ai_family, +- runp->ai_family == AF_INET +- ? (void *) &((struct sockaddr_in *) runp->ai_addr)->sin_addr +- : (void *) &((struct sockaddr_in6 *) runp->ai_addr)->sin6_addr, +- buf, sizeof (buf)); ++ if (getnameinfo (runp->ai_addr, runp->ai_addrlen, buf, sizeof (buf), NULL, 0, NI_NUMERICHOST) != 0) ++ { ++ freeaddrinfo(ai); ++ return NO; ++ } + +- if (are_addresses_equal(buf, tok, netmask_ptr)) ++ for (runp1 = ai; runp1 != NULL; runp1 = runp1->ai_next) + { +- return YES; ++ char buf1[INET6_ADDRSTRLEN]; ++ ++ if (runp->ai_family != runp1->ai_family) ++ continue; ++ ++ if (getnameinfo (runp1->ai_addr, runp1->ai_addrlen, buf1, sizeof (buf1), NULL, 0, NI_NUMERICHOST) != 0) ++ { ++ freeaddrinfo(ai); ++ return NO; ++ } ++ ++ if (are_addresses_equal (buf, buf1, netmask_ptr)) ++ { ++ freeaddrinfo(ai); ++ return YES; ++ } + } + runp = runp->ai_next; + } + } + } + else +- return (are_addresses_equal(string, tok, netmask_ptr)); ++ { ++ struct addrinfo *runp1; ++ ++ for (runp1 = ai; runp1 != NULL; runp1 = runp1->ai_next) ++ { ++ char buf1[INET6_ADDRSTRLEN]; ++ ++ (void) getnameinfo (runp1->ai_addr, runp1->ai_addrlen, buf1, sizeof (buf1), NULL, 0, NI_NUMERICHOST); ++ ++ if (are_addresses_equal(string, buf1, netmask_ptr)) ++ { ++ freeaddrinfo(ai); ++ return YES; ++ } ++ } ++ } ++ ++ freeaddrinfo(ai); + + return NO; + } diff --git a/SOURCES/pam-1.3.1-faillock-create-tallydir.patch b/SOURCES/pam-1.3.1-faillock-create-tallydir.patch new file mode 100644 index 0000000..e2d2caf --- /dev/null +++ b/SOURCES/pam-1.3.1-faillock-create-tallydir.patch @@ -0,0 +1,13 @@ +diff -up Linux-PAM-1.3.1/modules/pam_faillock/faillock.c.faillock-create-tallydir Linux-PAM-1.3.1/modules/pam_faillock/faillock.c +--- Linux-PAM-1.3.1/modules/pam_faillock/faillock.c.faillock-create-tallydir 2024-01-08 11:32:02.122392119 +0100 ++++ Linux-PAM-1.3.1/modules/pam_faillock/faillock.c 2024-01-08 11:33:10.916515943 +0100 +@@ -74,6 +74,9 @@ open_tally (const char *dir, const char + + if (create) { + flags |= O_CREAT; ++ if (access(dir, F_OK) != 0) { ++ mkdir(dir, 0755); ++ } + } + + fd = open(path, flags, 0600); diff --git a/SOURCES/pam-1.3.1-namespace-protect-dir.patch b/SOURCES/pam-1.3.1-namespace-protect-dir.patch new file mode 100644 index 0000000..39e2b5e --- /dev/null +++ b/SOURCES/pam-1.3.1-namespace-protect-dir.patch @@ -0,0 +1,58 @@ +From 031bb5a5d0d950253b68138b498dc93be69a64cb Mon Sep 17 00:00:00 2001 +From: Matthias Gerstner +Date: Wed, 27 Dec 2023 14:01:59 +0100 +Subject: [PATCH] pam_namespace: protect_dir(): use O_DIRECTORY to prevent + local DoS situations + +Without O_DIRECTORY the path crawling logic is subject to e.g. FIFOs +being placed in user controlled directories, causing the PAM module to +block indefinitely during `openat()`. + +Pass O_DIRECTORY to cause the `openat()` to fail if the path does not +refer to a directory. + +With this the check whether the final path element is a directory +becomes unnecessary, drop it. +--- + modules/pam_namespace/pam_namespace.c | 18 +----------------- + 1 file changed, 1 insertion(+), 17 deletions(-) + +diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c +index 2528cff8..f72d6718 100644 +--- a/modules/pam_namespace/pam_namespace.c ++++ b/modules/pam_namespace/pam_namespace.c +@@ -1201,7 +1201,7 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, + int dfd = AT_FDCWD; + int dfd_next; + int save_errno; +- int flags = O_RDONLY; ++ int flags = O_RDONLY | O_DIRECTORY; + int rv = -1; + struct stat st; + +@@ -1255,22 +1255,6 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, + rv = openat(dfd, dir, flags); + } + +- if (rv != -1) { +- if (fstat(rv, &st) != 0) { +- save_errno = errno; +- close(rv); +- rv = -1; +- errno = save_errno; +- goto error; +- } +- if (!S_ISDIR(st.st_mode)) { +- close(rv); +- errno = ENOTDIR; +- rv = -1; +- goto error; +- } +- } +- + if (flags & O_NOFOLLOW) { + /* we are inside user-owned dir - protect */ + if (protect_mount(rv, p, idata) == -1) { +-- +2.43.0 + diff --git a/SOURCES/pam-1.3.1-unix-default-rounds.patch b/SOURCES/pam-1.3.1-unix-default-rounds.patch new file mode 100644 index 0000000..bb4a156 --- /dev/null +++ b/SOURCES/pam-1.3.1-unix-default-rounds.patch @@ -0,0 +1,12 @@ +diff -up Linux-PAM-1.3.1/modules/pam_unix/pam_unix_passwd.c.unix-default-rounds Linux-PAM-1.3.1/modules/pam_unix/pam_unix_passwd.c +--- Linux-PAM-1.3.1/modules/pam_unix/pam_unix_passwd.c.unix-default-rounds 2023-11-02 09:59:54.533238124 +0100 ++++ Linux-PAM-1.3.1/modules/pam_unix/pam_unix_passwd.c 2023-11-02 10:40:58.017404936 +0100 +@@ -607,7 +607,7 @@ pam_sm_chauthtok(pam_handle_t *pamh, int + unsigned int ctrl, lctrl; + int retval; + int remember = -1; +- int rounds = -1; ++ int rounds = 0; + int pass_min_len = 0; + + /* */ diff --git a/SOURCES/pam-1.3.1-unix-enable-bcrypt.patch b/SOURCES/pam-1.3.1-unix-enable-bcrypt.patch new file mode 100644 index 0000000..9264765 --- /dev/null +++ b/SOURCES/pam-1.3.1-unix-enable-bcrypt.patch @@ -0,0 +1,174 @@ +From f7abb8c1ef3aa31e6c2564a8aaf69683a77c2016 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bj=C3=B6rn=20Esser?= +Date: Thu, 15 Nov 2018 15:01:57 +0100 +Subject: [PATCH] pam_unix: Use bcrypt b-variant for computing new hashes. + +Bcrypt hashes used the "$2a$" prefix since 1997. +However, in 2011 an implementation bug was discovered in bcrypt +affecting the handling of characters in passphrases with the 8th +bit set. + +Besides fixing the bug, OpenBSD 5.5 introduced the "$2b$" prefix +for a behavior that exactly matches crypt_blowfish's "$2y$", and +the crypt_blowfish implementation supports it as well since v1.1. + +That said new computed bcrypt hashes should use the "$2b$" prefix. + +* modules/pam_unix/passverify.c: Use bcrypt b-variant. +--- + modules/pam_unix/passverify.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c +index 9c1771e2..1f433b3a 100644 +--- a/modules/pam_unix/passverify.c ++++ b/modules/pam_unix/passverify.c +@@ -385,7 +385,7 @@ PAMH_ARG_DECL(char * create_password_hash, + /* algoid = "$1" */ + return crypt_md5_wrapper(password); + } else if (on(UNIX_BLOWFISH_PASS, ctrl)) { +- algoid = "$2a$"; ++ algoid = "$2b$"; + } else if (on(UNIX_SHA256_PASS, ctrl)) { + algoid = "$5$"; + } else if (on(UNIX_SHA512_PASS, ctrl)) { +-- +2.41.0 + +diff -up Linux-PAM-1.3.1/configure.ac.legacy-xcrypt Linux-PAM-1.3.1/configure.ac +--- Linux-PAM-1.3.1/configure.ac.legacy-xcrypt 2023-10-26 12:08:46.896437225 +0200 ++++ Linux-PAM-1.3.1/configure.ac 2023-10-26 12:10:38.289654696 +0200 +@@ -395,19 +395,32 @@ AC_SUBST(LIBAUDIT) + AM_CONDITIONAL([HAVE_AUDIT_TTY_STATUS], + [test "x$HAVE_AUDIT_TTY_STATUS" = xyes]) + +-AC_CHECK_HEADERS(xcrypt.h crypt.h) +-AS_IF([test "x$ac_cv_header_xcrypt_h" = "xyes"], +- [crypt_libs="xcrypt crypt"], +- [crypt_libs="crypt"]) ++AC_CHECK_HEADERS(crypt.h) + + BACKUP_LIBS=$LIBS +-AC_SEARCH_LIBS([crypt],[$crypt_libs], LIBCRYPT="${ac_lib:+-l$ac_lib}", LIBCRYPT="") +-AC_CHECK_FUNCS(crypt_r crypt_gensalt_r) ++LIBCRYPT="" ++PKG_CHECK_MODULES([CRYPT], [libcrypt], [ ++ CFLAGS="$CFLAGS $CRYPT_CFLAGS" ++ CPPFLAGS="$CPPFLAGS $CRYPT_CFLAGS" ++ LIBS="$LIBS $CRYPT_LIBS" ++ LIBCRYPT="$CRYPT_LIBS" ++], [ ++ AC_SEARCH_LIBS([crypt_gensalt_rn],[crypt]) ++ case "$ac_cv_search_crypt_gensalt_rn" in ++ -l*) LIBCRYPT="$ac_cv_search_crypt_gensalt_rn" ;; ++ no) AC_SEARCH_LIBS([crypt_r],[crypt]) ++ case "$ac_cv_search_crypt_r" in ++ -l*) LIBCRYPT="$ac_cv_search_crypt_r" ;; ++ no ) AC_SEARCH_LIBS([crypt],[crypt]) ++ case "$ac_cv_search_crypt" in ++ -l*) LIBCRYPT="$ac_cv_search_crypt" ;; ++ esac ;; ++ esac ;; ++ esac ++]) ++AC_CHECK_FUNCS([crypt_r]) + LIBS=$BACKUP_LIBS + AC_SUBST(LIBCRYPT) +-if test "$LIBCRYPT" = "-lxcrypt" -a "$ac_cv_header_xcrypt_h" = "yes" ; then +- AC_DEFINE([HAVE_LIBXCRYPT], 1, [Define to 1 if xcrypt support should be compiled in.]) +-fi + + AC_ARG_WITH([randomdev], AS_HELP_STRING([--with-randomdev=(|yes|no)],[use specified random device instead of /dev/urandom or 'no' to disable]), opt_randomdev=$withval) + if test "$opt_randomdev" = yes -o -z "$opt_randomdev"; then +diff -up Linux-PAM-1.3.1/modules/pam_pwhistory/opasswd.c.legacy-xcrypt Linux-PAM-1.3.1/modules/pam_pwhistory/opasswd.c +--- Linux-PAM-1.3.1/modules/pam_pwhistory/opasswd.c.legacy-xcrypt 2023-10-26 12:08:46.896437225 +0200 ++++ Linux-PAM-1.3.1/modules/pam_pwhistory/opasswd.c 2023-10-26 12:11:14.437725259 +0200 +@@ -52,9 +52,7 @@ + #include + #include + +-#if defined (HAVE_XCRYPT_H) +-#include +-#elif defined (HAVE_CRYPT_H) ++#ifdef HAVE_CRYPT_H + #include + #endif + +diff -up Linux-PAM-1.3.1/modules/pam_unix/bigcrypt.c.legacy-xcrypt Linux-PAM-1.3.1/modules/pam_unix/bigcrypt.c +--- Linux-PAM-1.3.1/modules/pam_unix/bigcrypt.c.legacy-xcrypt 2017-02-10 11:10:15.000000000 +0100 ++++ Linux-PAM-1.3.1/modules/pam_unix/bigcrypt.c 2023-10-26 12:08:46.896437225 +0200 +@@ -29,9 +29,7 @@ + #include + #include + #include +-#ifdef HAVE_LIBXCRYPT +-#include +-#elif defined(HAVE_CRYPT_H) ++#ifdef HAVE_CRYPT_H + #include + #endif + +diff -up Linux-PAM-1.3.1/modules/pam_unix/passverify.c.legacy-xcrypt Linux-PAM-1.3.1/modules/pam_unix/passverify.c +--- Linux-PAM-1.3.1/modules/pam_unix/passverify.c.legacy-xcrypt 2023-10-26 12:08:46.895437223 +0200 ++++ Linux-PAM-1.3.1/modules/pam_unix/passverify.c 2023-10-26 12:16:25.470320408 +0200 +@@ -19,9 +19,7 @@ + #include + #include + #include +-#ifdef HAVE_LIBXCRYPT +-#include +-#elif defined(HAVE_CRYPT_H) ++#ifdef HAVE_CRYPT_H + #include + #endif + +@@ -406,23 +404,19 @@ PAMH_ARG_DECL(char * create_password_has + return crypted; + } + +-#ifdef HAVE_CRYPT_GENSALT_R +- if (on(UNIX_BLOWFISH_PASS, ctrl)) { +- char entropy[17]; +- crypt_make_salt(entropy, sizeof(entropy) - 1); +- sp = crypt_gensalt_r (algoid, rounds, +- entropy, sizeof(entropy), +- salt, sizeof(salt)); +- } else { +-#endif +- sp = stpcpy(salt, algoid); +- if (on(UNIX_ALGO_ROUNDS, ctrl)) { +- sp += snprintf(sp, sizeof(salt) - (16 + 1 + (sp - salt)), "rounds=%u$", rounds); +- } +- crypt_make_salt(sp, 16); +-#ifdef HAVE_CRYPT_GENSALT_R ++#if defined(CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY) && CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY ++ /* ++ * Any version of libcrypt supporting auto entropy is ++ * guaranteed to have crypt_gensalt_rn(). ++ */ ++ sp = crypt_gensalt_rn(algoid, rounds, NULL, 0, salt, sizeof(salt)); ++#else ++ sp = stpcpy(salt, algoid); ++ if (on(UNIX_ALGO_ROUNDS, ctrl)) { ++ sp += snprintf(sp, sizeof(salt) - (16 + 1 + (sp - salt)), "rounds=%u$", rounds); + } +-#endif ++ crypt_make_salt(sp, 16); ++#endif /* CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY */ + #ifdef HAVE_CRYPT_R + sp = NULL; + cdata = malloc(sizeof(*cdata)); +diff -up Linux-PAM-1.3.1/modules/pam_userdb/pam_userdb.c.legacy-xcrypt Linux-PAM-1.3.1/modules/pam_userdb/pam_userdb.c +--- Linux-PAM-1.3.1/modules/pam_userdb/pam_userdb.c.legacy-xcrypt 2023-10-26 12:08:46.880437194 +0200 ++++ Linux-PAM-1.3.1/modules/pam_userdb/pam_userdb.c 2023-10-26 12:08:46.896437225 +0200 +@@ -17,9 +17,7 @@ + #include + #include + #include +-#ifdef HAVE_LIBXCRYPT +-#include +-#elif defined(HAVE_CRYPT_H) ++#ifdef HAVE_CRYPT_H + #include + #endif + diff --git a/SPECS/pam.spec b/SPECS/pam.spec index ef59553..d9c2250 100644 --- a/SPECS/pam.spec +++ b/SPECS/pam.spec @@ -3,7 +3,7 @@ Summary: An extensible library which provides authentication for applications Name: pam Version: 1.3.1 -Release: 27%{?dist} +Release: 33%{?dist} # The library is BSD licensed with option to relicense as GPLv2+ # - this option is redundant as the BSD license allows that anyway. # pam_timestamp, pam_loginuid, and pam_console modules are GPLv2+. @@ -100,6 +100,16 @@ Patch64: pam-1.5.1-pam-faillock-avoid-logging-erroneous.patch # https://github.com/linux-pam/linux-pam/commit/55f206447a1e4ee26e307e7a9c069236e823b1a5 # https://github.com/linux-pam/linux-pam/commit/80bfda5962e5be3daa70e0fc8c75fc97d1c55121 Patch65: pam-1.3.1-pam-misc-configurable.patch +# https://github.com/linux-pam/linux-pam/commit/530c9f9e2d746e1d168c6b17863debda7664ac7c +# https://github.com/linux-pam/linux-pam/commit/f7abb8c1ef3aa31e6c2564a8aaf69683a77c2016 +Patch66: pam-1.3.1-unix-enable-bcrypt.patch +Patch67: pam-1.3.1-unix-default-rounds.patch +# https://github.com/linux-pam/linux-pam/commit/d54870f993e97fe75e2cd0470a3701d5af22877c +Patch68: pam-1.3.1-faillock-create-tallydir.patch +# https://github.com/linux-pam/linux-pam/commit/23393bef92c1e768eda329813d7af55481c6ca9f +Patch69: pam-1.3.1-access-handle-hostnames.patch +# https://github.com/linux-pam/linux-pam/commit/031bb5a5d0d950253b68138b498dc93be69a64cb +Patch70: pam-1.3.1-namespace-protect-dir.patch %define _pamlibdir %{_libdir} %define _moduledir %{_libdir}/security @@ -213,6 +223,11 @@ cp %{SOURCE18} . %patch63 -p1 -b .pam-faillock-clarify-missing-user %patch64 -p1 -b .pam-faillock-avoid-logging-erroneous %patch65 -p1 -b .pam-misc-configurable +%patch66 -p1 -b .unix-enable-bcrypt +%patch67 -p1 -b .unix-default-rounds +%patch68 -p1 -b .faillock-create-tallydir +%patch69 -p1 -b .access-handle-hostnames +%patch70 -p1 -b .namespace-protect-dir autoreconf -i @@ -466,6 +481,19 @@ done %doc doc/specs/rfc86.0.txt %changelog +* Mon Feb 12 2024 Iker Pedrosa - 1.3.1-33 +- pam_namespace: protect_dir(): use O_DIRECTORY to prevent local DoS + situations. CVE-2024-22365. Resolves: RHEL-21242 + +* Fri Jan 26 2024 Iker Pedrosa - 1.3.1-32 +- pam_access: handle hostnames in access.conf. Resolves: RHEL-3374 + +* Mon Jan 8 2024 Iker Pedrosa - 1.3.1-31 +- pam_faillock: create tallydir before creating tallyfile. Resolves: RHEL-19810 + +* Thu Nov 2 2023 Iker Pedrosa - 1.3.1-30 +- pam_unix: enable bcrypt. Resolves: RHEL-5057 + * Mon Jun 26 2023 Iker Pedrosa - 1.3.1-27 - pam_misc: make length of misc_conv() configurable and set to 4096. Resolves: #2209785