diff options
Diffstat (limited to 'community/sphinx')
-rw-r--r-- | community/sphinx/APKBUILD | 35 | ||||
-rw-r--r-- | community/sphinx/CVE-2020-29050.patch | 416 |
2 files changed, 438 insertions, 13 deletions
diff --git a/community/sphinx/APKBUILD b/community/sphinx/APKBUILD index 0f56d82ca55..22a73b2eb24 100644 --- a/community/sphinx/APKBUILD +++ b/community/sphinx/APKBUILD @@ -3,13 +3,18 @@ # Maintainer: Francesco Colista <fcolista@alpinelinux.org> pkgname=sphinx pkgver=2.2.11 -pkgrel=2 +pkgrel=7 pkgdesc="Free open-source SQL full-text search engine" url="http://www.sphinxsearch.com" arch="all" license="GPL-2.0-only" -makedepends="postgresql-dev mariadb-connector-c-dev unixodbc-dev expat-dev - re2-dev snowball-dev" +makedepends=" + expat-dev + libpq-dev + libstemmer-dev + mariadb-connector-c-dev + unixodbc-dev + " pkgusers="$pkgname" pkggroups="$pkgname" subpackages="$pkgname-doc $pkgname-php::noarch" @@ -17,10 +22,14 @@ source="http://sphinxsearch.com/files/sphinx-$pkgver-release.tar.gz sphinx.initd sphinx.confd sphinx-pagesize.patch + CVE-2020-29050.patch " - builddir="$srcdir"/$pkgname-$pkgver-release +# secfixes: +# 2.2.11-r7: +# - CVE-2020-29059 + build() { ./configure \ --prefix=/usr \ @@ -36,29 +45,26 @@ build() { --with-iconv \ --with-libstemmer \ --with-syslog \ - --with-re2 \ + --without-re2 \ --enable-id64 make } package() { make DESTDIR="$pkgdir" install - install -Dm755 "$srcdir"/$pkgname.confd "$pkgdir"/etc/conf.d/$pkgname install -Dm755 "$srcdir"/$pkgname.initd "$pkgdir"/etc/init.d/$pkgname + install -Dm644 "$srcdir"/$pkgname.confd "$pkgdir"/etc/conf.d/$pkgname chown -R $pkgusers:$pkggroups "$pkgdir"/var/lib/sphinx } - php() { pkgdesc="PHP api for sphinx search engine" mkdir -p "$subpkgdir"/usr/share/php/$pkgname/api - for i in $(ls $builddir/api/*.php); do - mv $i "$subpkgdir"/usr/share/php/$pkgname/api ; - done + find $builddir/api/ -type f -maxdepth 1 -name "*.php" \ + -exec mv {} "$subpkgdir"/usr/share/php/$pkgname/api/ \; } - doc() { default_doc; mkdir -p "$subpkgdir"/usr/share/doc/$pkgname/misc @@ -66,7 +72,10 @@ doc() { } -sha512sums="cf1a262a5b0fbf0bd2827ec6ec629edeaf709ce855a6e7b509b65342baaeb26c02717ca63f1578d32c83d21e2fd6d1e92dceb34660e6351b93cd96fd4e623689 sphinx-2.2.11-release.tar.gz +sha512sums=" +cf1a262a5b0fbf0bd2827ec6ec629edeaf709ce855a6e7b509b65342baaeb26c02717ca63f1578d32c83d21e2fd6d1e92dceb34660e6351b93cd96fd4e623689 sphinx-2.2.11-release.tar.gz 583601ff63e663099ae40048b8a216d0bc815a50a82370a42d3e7b923c90c650d58951636041ff9000141d897357767b7895a238a4edc49c328e46241b391350 sphinx.initd 8dbbb3b75bfbde5c6d2bee801df8c7a82650d3943dd667a4330cae473cbf18390aff5eb8d6aa6e5d69c4c995065d48289047b9166fa756c6015bf71f2b13a8f0 sphinx.confd -9563c5a926e5be30477781038ccf115a809a32bbcbc02c5b82e7985fca76185005968b5f0442772ec598b2ff17ef5c185582e24ae74775e5358abc88192345f2 sphinx-pagesize.patch" +9563c5a926e5be30477781038ccf115a809a32bbcbc02c5b82e7985fca76185005968b5f0442772ec598b2ff17ef5c185582e24ae74775e5358abc88192345f2 sphinx-pagesize.patch +b3e27a556789af966ca779136122f8d0ebb6999ef9766029a0492bcf918d3e6f94cda01797c8715ad41f6fa8f8afc755672734b4bdcacb17b35009ac9a5f05aa CVE-2020-29050.patch +" diff --git a/community/sphinx/CVE-2020-29050.patch b/community/sphinx/CVE-2020-29050.patch new file mode 100644 index 00000000000..62355266ce3 --- /dev/null +++ b/community/sphinx/CVE-2020-29050.patch @@ -0,0 +1,416 @@ +Patch-Source: https://salsa.debian.org/debian/sphinxsearch/-/blob/5810cd80b48ec7915458939f992030f8b6f3bef9/debian/patches/06-CVE-2020-29050.patch +-- +From: klirichek <alexey@manticoresearch.com> +Date: Tue, 2 Jul 2019 19:06:09 +0700 +Subject: [PATCH] Fix random file reading by scattered snippets + +Issue was that load_files_scattered option just concatenated prefix with +given name and then opened the file. So, if you provide something like +'/etc/password' as file, or '../../../etc/passwd' even with non-zero +prefix, it will unfortunately work. After this commit such behavior +possible ONLY if user explicitly set `snippets_file_prefix` to empty +string or '/'; another cases will not cause uncontroled reading. +That fixes #866. + +[basilgello: Back-port to 2.2.11: + - Refactor sphNormalizePath to use only available classes. + - Do not use 'g_sSnippetsFilePrefix' in src/sphinxexcerpt.h, + use 'tOptions.m_sFilePrefix' instead, as for 2.2.11, all + two instances are assigned to 'g_sSnippetsFilePrefix'. + - Adjust context. ] + +The following test passes, adapted from commit +66b5761ad258c60b1866a8e1333f86e74f48035 at +https://github.com/manticoresoftware/manticoresearch : + +==== +/* + cd test && + g++ -o test test.cpp -I../src -I../config -I/usr/include/postgresql \ + -I/usr/include/mariadb -DHAVE_CONFIG_H -L../src -lsphinx -pthread \ + -lm -ldl -lstemmer -lmariadb -lpq -lexpat -lz +*/ + +void ASSERT_STREQ(const char *src, const char *dst) +{ + auto normalized = sphNormalizePath(src); + if (!strcmp(dst, normalized.scstr())) { + auto _src = src; + auto _dst = dst; + + if (src == nullptr) _src = "nullptr"; + if (dst == nullptr) _dst = "nullptr"; + + std::cout << "OK : '" << _src << "' = '" << _dst << + "'" << std::endl; + } else { + auto _src = src; + auto _dst = dst; + + if (src == nullptr) _src = "nullptr"; + if (dst == nullptr) _dst = "nullptr"; + + std::cout << "FAIL: '" << _src << "' != '" << _dst << "' ( = '" << + normalized.scstr() << "')" << std::endl; + } +} + +int main() +{ + ASSERT_STREQ ( "/", "/" ); + ASSERT_STREQ ( "/..//bbb", "/bbb" ); + ASSERT_STREQ ( "/quite/long/path/../../../etc/passwd", "/etc/passwd" ); + ASSERT_STREQ ( "/aaa/bbb/ccc/ddd/../../../../../../../", "/" ); + + ASSERT_STREQ ( "", "" ); + ASSERT_STREQ ( nullptr, "" ); + ASSERT_STREQ ( "aaa/", "aaa" ); + ASSERT_STREQ ( "aaa/.", "aaa" ); + ASSERT_STREQ ( "aaa/././././////././", "aaa" ); + ASSERT_STREQ ( "aaa/////", "aaa" ); + ASSERT_STREQ ( "aaa/bbb/ccc", "aaa/bbb/ccc" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/..", "aaa/bbb/ccc" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/../../..", "aaa" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/../../../xxx", "aaa/xxx" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/../../../..", "" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/../../../../", "" ); + ASSERT_STREQ ( "aaa/bbb/ccc/ddd/../../../../../../../", "../../.." ); + ASSERT_STREQ ( "..//bbb", "../bbb" ); + return 0; +} +==== + +Fixes CVE-2020-29050. + +Signed-off-by: Vasyl Gello <vasek.gello@gmail.com> +--- + doc/sphinx.html | 19 +++++++++++++- + doc/sphinx.txt | 19 +++++++++++++- + sphinx.conf.in | 4 +-- + src/searchd.cpp | 8 +++++- + src/sphinx.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ + src/sphinxexcerpt.cpp | 14 ++++++++++ + src/sphinxexcerpt.h | 3 +++ + src/sphinxstd.h | 3 +++ + test/test_130/test.xml | 7 ++--- + 9 files changed, 140 insertions(+), 8 deletions(-) + +diff --git a/doc/sphinx.html b/doc/sphinx.html +index b56e445..8ad44dd 100644 +--- a/doc/sphinx.html ++++ b/doc/sphinx.html +@@ -11652,7 +11652,7 @@ binlog_max_log_size = 16M + <div class="sect2" title="12.4.28. snippets_file_prefix"><div class="titlepage"><div><div><h3 class="title"><a name="conf-snippets-file-prefix"></a>12.4.28. snippets_file_prefix</h3></div></div></div> + <p> + A prefix to prepend to the local file names when generating snippets. +-Optional, default is empty. ++Optional, default is current working folder. + Introduced in version 2.1.1-beta. + </p><p> + This prefix can be used in distributed snippets generation along with +@@ -11663,6 +11663,19 @@ is set to "server1" and the request refers to "file23", <code class="filename">s + will attempt to open "server1file23" (all of that without quotes). So if you + need it to be a path, you have to mention the trailing slash. + </p><p> ++After constructing final file path, daemon unwinds all relative dirs and ++compares final result with the value of ``snippet_file_prefix``. If result ++is not begin with the prefix, such file will be rejected with error message. ++ ++So, if you set it to '/mnt/data' and somebody calls snippet generation with file ++'../../../etc/passwd', as the source, it will get error message ++`File '/mnt/data/../../../etc/passwd' escapes '/mnt/data/' scope` ++instead of content of the file. ++ ++Also, with non-set parameter and reading '/etc/passwd' it will actually read ++/daemon/working/folder/etc/passwd since default for param is exactly daemon's ++working folder. ++</p><p> + Note also that this is a local option, it does not affect the agents anyhow. + So you can safely set a prefix on a master server. The requests routed to the + agents will not be affected by the master's setting. They will however be affected +@@ -11673,6 +11686,10 @@ This might be useful, for instance, when the document storage locations + </p><h4><a name="idp33320288"></a>Example:</h4><pre class="programlisting"> + snippets_file_prefix = /mnt/common/server1/ + </pre></div> ++<p><span class="bold"><strong>WARNING:</strong></span> ++If you still want to access files from the FS root, you have to explicitly set ++'snippets_file_prefix' to empty value (by 'snippets_file_prefix=' line), or to ++root (by 'snippets_file_prefix=/'). + <div class="sect2" title="12.4.29. collation_server"><div class="titlepage"><div><div><h3 class="title"><a name="conf-collation-server"></a>12.4.29. collation_server</h3></div></div></div> + <p> + Default server collation. +diff --git a/doc/sphinx.txt b/doc/sphinx.txt +index ed994f9..f750f4e 100644 +--- a/doc/sphinx.txt ++++ b/doc/sphinx.txt +@@ -12832,7 +12832,7 @@ Example: + ----------------------------- + + A prefix to prepend to the local file names when generating snippets. +-Optional, default is empty. Introduced in version 2.1.1-beta. ++Optional, default is current working folder. Introduced in version 2.1.1-beta. + + This prefix can be used in distributed snippets generation along with + load_files or load_files_scattered options. +@@ -12842,6 +12842,19 @@ to "server1" and the request refers to "file23", searchd will attempt to + open "server1file23" (all of that without quotes). So if you need it to be + a path, you have to mention the trailing slash. + ++After constructing final file path, daemon unwinds all relative dirs and ++compares final result with the value of ``snippet_file_prefix``. If result ++is not begin with the prefix, such file will be rejected with error message. ++ ++So, if you set it to '/mnt/data' and somebody calls snippet generation with file ++'../../../etc/passwd', as the source, it will get error message ++`File '/mnt/data/../../../etc/passwd' escapes '/mnt/data/' scope` ++instead of content of the file. ++ ++Also, with non-set parameter and reading '/etc/passwd' it will actually read ++/daemon/working/folder/etc/passwd since default for param is exactly daemon's ++working folder. ++ + Note also that this is a local option, it does not affect the agents + anyhow. So you can safely set a prefix on a master server. The requests + routed to the agents will not be affected by the master's setting. They +@@ -12855,6 +12868,10 @@ Example: + + | snippets_file_prefix = /mnt/common/server1/ + ++WARNING: If you still want to access files from the FS root, you have to ++explicitly set 'snippets_file_prefix' to empty value (by 'snippets_file_prefix=' ++line), or to root (by 'snippets_file_prefix=/'). ++ + 12.4.29. collation_server + ------------------------- + +diff --git a/sphinx.conf.in b/sphinx.conf.in +index 0ccf858..9d83897 100644 +--- a/sphinx.conf.in ++++ b/sphinx.conf.in +@@ -604,7 +604,7 @@ index test1 + # snippet document file name prefix + # preprended to file names when generating snippets using load_files option + # WARNING, this is a prefix (not a path), trailing slash matters! +- # optional, default is empty ++ # optional, default is current working directory of a running process + # + # snippets_file_prefix = /mnt/mydocs/server1 + +@@ -1042,7 +1042,7 @@ searchd + + # a prefix to prepend to the local file names when creating snippets + # with load_files and/or load_files_scatter options +- # optional, default is empty ++ # optional, default is current working directory of a running process + # + # snippets_file_prefix = /mnt/common/server1/ + } +diff --git a/src/searchd.cpp b/src/searchd.cpp +index 85b1cd6..6462b16 100644 +--- a/src/searchd.cpp ++++ b/src/searchd.cpp +@@ -13330,6 +13330,12 @@ bool MakeSnippets ( CSphString sIndex, CSphVector<ExcerptQuery_t> & dQueries, CS + struct stat st; + CSphString sFilename; + sFilename.SetSprintf ( "%s%s", g_sSnippetsFilePrefix.cstr(), dQueries[i].m_sSource.cstr() ); ++ if ( !TestEscaping ( g_sSnippetsFilePrefix, sFilename )) ++ { ++ sError.SetSprintf( "File '%s' escapes '%s' scope", ++ sFilename.scstr(), g_sSnippetsFilePrefix.scstr()); ++ return false; ++ } + if ( ::stat ( sFilename.cstr(), &st )<0 ) + { + if ( !bScattered ) +@@ -23719,7 +23725,7 @@ int WINAPI ServiceMain ( int argc, char **argv ) + if ( hSearchd.Exists ( "snippets_file_prefix" ) ) + g_sSnippetsFilePrefix = hSearchd["snippets_file_prefix"].cstr(); + else +- g_sSnippetsFilePrefix = ""; ++ g_sSnippetsFilePrefix.SetSprintf("%s/", sphGetCwd().scstr()); + + const char* sLogFormat = hSearchd.GetStr ( "query_log_format", "plain" ); + if ( !strcmp ( sLogFormat, "sphinxql" ) ) +diff --git a/src/sphinx.cpp b/src/sphinx.cpp +index 698b8af..4257d0f 100644 +--- a/src/sphinx.cpp ++++ b/src/sphinx.cpp +@@ -81,9 +81,11 @@ + #include <io.h> // for open() + + // workaround Windows quirks ++ #include <direct.h> + #define popen _popen + #define pclose _pclose + #define snprintf _snprintf ++ #define getcwd _getcwd + #define sphSeek _lseeki64 + + #define stat _stat64 +@@ -12420,6 +12422,75 @@ static bool sphTruncate ( int iFD ) + #endif + } + ++CSphString sphNormalizePath( const CSphString& sOrigPath ) ++{ ++ CSphVector<CSphString> dChunks; ++ const char* sBegin = sOrigPath.scstr(); ++ const char* sEnd = sBegin + sOrigPath.Length(); ++ const char* sPath = sBegin; ++ int iLevel = 0; ++ ++ while ( sPath<sEnd ) ++ { ++ const char* sSlash = ( char* ) memchr( sPath, '/', sEnd - sPath ); ++ if ( !sSlash ) ++ sSlash = sEnd; ++ ++ auto iChunkLen = sSlash - sPath; ++ ++ switch ( iChunkLen ) ++ { ++ case 0: // empty chunk skipped ++ ++sPath; ++ continue; ++ case 1: // simple dot chunk skipped ++ if ( *sPath=='.' ) ++ { ++ sPath += 2; ++ continue; ++ } ++ break; ++ case 2: // double dot abandons chunks, then decrease level ++ if ( sPath[0]=='.' && sPath[1]=='.' ) ++ { ++ if ( dChunks.GetLength() <= 0 ) ++ --iLevel; ++ else ++ dChunks.Pop(); ++ sPath += 3; ++ continue; ++ } ++ default: break; ++ } ++ CSphString temp( "" ); ++ temp.SetBinary( sPath, iChunkLen ); ++ dChunks.Add( temp ); ++ sPath = sSlash + 1; ++ } ++ ++ CSphStringBuilder sResult; ++ if ( *sBegin=='/' ) ++ sResult += "/"; ++ else ++ while ( iLevel++<0 ) ++ dChunks.Insert(0, ".."); ++ ++ int i; ++ for ( i=0; i<dChunks.GetLength(); i++ ) { ++ sResult += dChunks[i].scstr(); ++ if (i<dChunks.GetLength()-1) ++ sResult += "/"; ++ } ++ ++ return sResult.cstr(); ++} ++ ++CSphString sphGetCwd() ++{ ++ CSphVector<char> sBuf (65536); ++ return getcwd( sBuf.Begin(), sBuf.GetLength()); ++} ++ + class DeleteOnFail : public ISphNoncopyable + { + public: +diff --git a/src/sphinxexcerpt.cpp b/src/sphinxexcerpt.cpp +index b42c6a2..ff593f0 100644 +--- a/src/sphinxexcerpt.cpp ++++ b/src/sphinxexcerpt.cpp +@@ -3817,6 +3817,11 @@ void sphBuildExcerpt ( ExcerptQuery_t & tOptions, const CSphIndex * pIndex, cons + { + CSphString sFilename; + sFilename.SetSprintf ( "%s%s", tOptions.m_sFilePrefix.cstr(), tOptions.m_sSource.cstr() ); ++ if ( !TestEscaping( tOptions.m_sFilePrefix.scstr(), sFilename )) ++ { ++ sError.SetSprintf( "File '%s' escapes '%s' scope", sFilename.scstr(), tOptions.m_sFilePrefix.scstr()); ++ return; ++ } + if ( tFile.Open ( sFilename.cstr(), SPH_O_READ, sError )<0 ) + return; + } else if ( tOptions.m_sSource.IsEmpty() ) +@@ -3859,6 +3864,15 @@ void sphBuildExcerpt ( ExcerptQuery_t & tOptions, const CSphIndex * pIndex, cons + sWarning, sError, pQueryTokenizer, tOptions.m_dRes ); + } + ++// check whether filepath from sPath does not escape area of sPrefix ++bool TestEscaping( const CSphString& sPrefix, const CSphString& sPath ) ++{ ++ if ( sPrefix.IsEmpty() || sPrefix==sPath ) ++ return true; ++ auto sNormalized = sphNormalizePath( sPath ); ++ return sPrefix==sNormalized.SubString( 0, sPrefix.Length()); ++} ++ + // + // $Id$ + // +diff --git a/src/sphinxexcerpt.h b/src/sphinxexcerpt.h +index cf48b1f..87a55d4 100644 +--- a/src/sphinxexcerpt.h ++++ b/src/sphinxexcerpt.h +@@ -81,6 +81,9 @@ struct XQQuery_t; + void sphBuildExcerpt ( ExcerptQuery_t & tOptions, const CSphIndex * pIndex, const CSphHTMLStripper * pStripper, const XQQuery_t & tExtQuery, + DWORD eExtQuerySPZ, CSphString & sWarning, CSphString & sError, CSphDict * pDict, ISphTokenizer * pDocTokenizer, ISphTokenizer * pQueryTokenizer ); + ++// helper whether filepath from sPath does not escape area of sPrefix ++bool TestEscaping( const CSphString& sPrefix, const CSphString& sPath ); ++ + #endif // _sphinxexcerpt_ + + // +diff --git a/src/sphinxstd.h b/src/sphinxstd.h +index 39cc7ee..c1f15c1 100644 +--- a/src/sphinxstd.h ++++ b/src/sphinxstd.h +@@ -2294,6 +2294,9 @@ int sphOpenFile ( const char * sFile, CSphString & sError ); + /// return size of file descriptor + int64_t sphGetFileSize ( int iFD, CSphString & sError ); + ++// unwind different tricks like "../../../etc/passwd" ++CSphString sphNormalizePath ( const CSphString& sOrigPath ); ++CSphString sphGetCwd(); + + /// buffer trait that neither own buffer nor clean-up it on destroy + template < typename T > +diff --git a/test/test_130/test.xml b/test/test_130/test.xml +index d8f746c..41e4961 100644 +--- a/test/test_130/test.xml ++++ b/test/test_130/test.xml +@@ -7,6 +7,7 @@ + searchd + { + <searchd_settings/> ++ snippets_file_prefix=<this_test/>/ + } + + source test +@@ -30,15 +31,15 @@ index test + + $results = array(); + +-$docs = array( 'test_130/load_file.txt' ); ++$docs = array( "load_file.txt" ); + $opts = array( 'load_files'=>true, 'limit'=>0 ); + + $results[] = $client->BuildExcerpts($docs, 'test', 'end point', $opts ); + $results[] = $client->BuildExcerpts($docs, 'test', 'not_found', $opts ); +-$results[] = $client->BuildExcerpts(array( 'test_130/empty.txt' ), 'test', 'end point', $opts ); ++$results[] = $client->BuildExcerpts(array( 'empty.txt' ), 'test', 'end point', $opts ); + $results[] = $client->BuildExcerpts(array( '' ), 'test', 'not_found', $opts ); + $results[] = $client->GetLastError(); +-$results[] = $client->BuildExcerpts ( array ( 'test_130/512k.xml' ), 'test', 'it builds', array ( "limit" => 100, "load_files" => true ) ); ++$results[] = $client->BuildExcerpts ( array ( '512k.xml' ), 'test', 'it builds', array ( "limit" => 100, "load_files" => true ) ); + + ]]></custom_test> + |