Compare commits

..

10 Commits

Author SHA1 Message Date
rakshasa 3787dbe7f3 Tagged release 0.16.23. 2026-09-15 14:49:21 +02:00
noctuum 282c86597a Fix a use after free in the xmlrpc-c index path
The string was freed before it was tested, and tmp was never released.
2026-09-14 11:32:11 +02:00
noctuum c41e63248a Check the flush and sync on session file writes
good() was checked before close(), where the data is actually flushed.
2026-09-14 11:10:12 +02:00
noctuum 641626029a Fix a one byte write past address_copy
The index bound was the buffer size, not the last writable index.
2026-09-14 10:53:12 +02:00
noctuum 0c507c581f Release the zlib deflate state in gzip_compress_to_vector.
deflateEnd was never called, leaking 168 kB per compressed response.
2026-09-13 10:43:16 +02:00
noctuum af0b600b54 Add unit tests for ipv4_range_parse.
Covers single addresses, explicit ranges and the cidr mask boundaries.
2026-09-13 10:05:39 +02:00
noctuum c984a69fd1 Fix cidr /32 ip filter entries covering everything above the address.
Shifting end_mask by the full type width is undefined behavior.
2026-09-13 10:05:39 +02:00
Jakob Breivik Grimstveit 20002ce937 Fix false invalid session error on empty URI
Avoid triggering 'Session data is invalid' when loading downloads with an empty URI.
2026-09-11 12:05:59 +02:00
rakshasa 3caa31113d Bumped scgi max content length to 64mb. 2026-09-06 14:50:19 +02:00
Nicolas PARLANT 0373d227c1 add missing headers for libcxx-23
Signed-off-by: Nicolas PARLANT <nicolas.parlant@parhuet.fr>
2026-09-03 09:59:12 +02:00
11 changed files with 129 additions and 13 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.22],[sundell.software@gmail.com])
AC_INIT([rtorrent],[0.16.23],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [26], [api version])
AC_DEFINE([API_VERSION], [27], [api version])
RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS
@@ -49,7 +49,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.22])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.23])
AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C
+4 -1
View File
@@ -131,7 +131,7 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
// copy everything up to '#' to address_copy and work from there
while(address[address_start_index] != '#' && address[address_start_index] != '\r' &&
address[address_start_index] != '\n' && address[address_start_index] != '\0' &&
address_start_index < 4096 ) {
address_start_index < 4095 ) {
address_copy[address_start_index] = address[address_start_index];
address_start_index++;
@@ -206,6 +206,9 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
if (mask_bits == 0) {
mask = 0;
end_mask = ~(uint32_t)0;
} else if (mask_bits == 32) {
mask = ~(uint32_t)0;
end_mask = 0;
} else {
mask = (~mask) << (32-mask_bits);
end_mask = (~end_mask) >> mask_bits;
+18 -4
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <sstream>
#include <stdexcept>
#include <sys/stat.h>
#include <torrent/utils/log.h>
#include <torrent/utils/resume.h>
#include <torrent/object.h>
@@ -39,6 +40,11 @@ static constexpr const char* session_invalid_message = "Session data is invalid,
static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename, bool* is_invalid) {
struct stat sb;
if (stat(filename, &sb) != 0 || !S_ISREG(sb.st_mode))
return std::unique_ptr<torrent::Object>();
std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open())
@@ -167,11 +173,19 @@ void
DownloadFactory::receive_success() {
bool session_invalid = false;
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
std::unique_ptr<torrent::Object> rtorrent_object;
std::unique_ptr<torrent::Object> libtorrent_resume_object;
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
if (m_session) {
if (m_uri.empty())
throw torrent::input_error("Session torrent URI is empty.");
rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
}
uint32_t tracker_key;
+1 -1
View File
@@ -16,7 +16,7 @@ class SCgiTask : public torrent::system::Event {
public:
static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000;
static constexpr int max_content_size = (2 << 23);
static constexpr int max_content_size = (1 << 26);
static constexpr auto timeout_request = std::chrono::seconds(60);
+3 -1
View File
@@ -77,16 +77,18 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
{
const char* str;
xmlrpc_read_string(env, tmp, &str);
xmlrpc_DECREF(tmp);
if (env->fault_occurred)
throw xmlrpc_error_c(env);
const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0);
bool invalid = *str == '\0' || *end != '\0';
::free((void*)str);
if (*str == '\0' || *end != '\0')
if (invalid)
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3;
+13 -3
View File
@@ -127,8 +127,12 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
if (!output.good())
throw torrent::storage_error("failed to write stream to file : " + path);
// The data only reaches the kernel here, so this is where a full disk is seen.
output.close();
if (!output.good())
throw torrent::storage_error("failed to flush stream to file : " + path);
// Ensure that the new file is actually written to the disk
int fd = ::open(path.c_str(), O_WRONLY);
@@ -137,13 +141,19 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
if (use_fsyncdisk) {
#ifdef __APPLE__
::fsync(fd);
int sync_result = ::fsync(fd);
#else
::fdatasync(fd);
int sync_result = ::fdatasync(fd);
#endif
if (sync_result == -1) {
::close(fd);
throw torrent::storage_error("failed to sync file to disk : " + path);
}
}
::close(fd);
if (::close(fd) == -1)
throw torrent::storage_error("failed to close file descriptor : " + path);
}
} // namespace anonymous
+1
View File
@@ -3,6 +3,7 @@
#include <functional>
#include <string>
#include <vector>
int parse_main_options(int argc, char** argv);
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
+4
View File
@@ -5,6 +5,8 @@
#include <zlib.h>
#include <torrent/exceptions.h>
#include "utils/functional.h"
namespace utils {
void
@@ -21,6 +23,8 @@ gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<cha
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK)
throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate.");
scope_guard guard([&zs]() { deflateEnd(&zs); });
auto max_response_size = deflateBound(&zs, length);
output.resize(offset + max_response_size);
+2
View File
@@ -51,6 +51,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \
src/test_command_dynamic.h \
src/test_command_ip.cc \
src/test_command_ip.h \
src/test_command_system.cc \
src/test_command_system.h \
src/test_command_path.cc \
+55
View File
@@ -0,0 +1,55 @@
#include "config.h"
#include "test/src/test_command_ip.h"
#include <cstdint>
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandIp);
bool ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address_end);
static uint32_t
ipv4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
return (a << 24) | (b << 16) | (c << 8) | d;
}
#define RANGE_ASSERT(address, expected_start, expected_end) \
{ \
uint32_t start = 0; \
uint32_t end = 0; \
\
CPPUNIT_ASSERT(ipv4_range_parse(address, &start, &end)); \
CPPUNIT_ASSERT_EQUAL(expected_start, start); \
CPPUNIT_ASSERT_EQUAL(expected_end, end); \
}
void
TestCommandIp::test_single_address() {
RANGE_ASSERT("10.1.2.3", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3));
}
void
TestCommandIp::test_explicit_range() {
RANGE_ASSERT("10.1.2.3-10.1.2.9", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 9));
}
void
TestCommandIp::test_cidr() {
RANGE_ASSERT("10.0.0.0/8", ipv4(10, 0, 0, 0), ipv4(10, 255, 255, 255));
RANGE_ASSERT("10.1.2.0/24", ipv4(10, 1, 2, 0), ipv4(10, 1, 2, 255));
RANGE_ASSERT("10.1.2.128/25", ipv4(10, 1, 2, 128), ipv4(10, 1, 2, 255));
RANGE_ASSERT("10.1.2.3/31", ipv4(10, 1, 2, 2), ipv4(10, 1, 2, 3));
}
void
TestCommandIp::test_cidr_zero_mask() {
RANGE_ASSERT("0.0.0.0/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255));
RANGE_ASSERT("10.1.2.3/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255));
}
void
TestCommandIp::test_cidr_full_mask() {
RANGE_ASSERT("10.1.2.3/32", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3));
RANGE_ASSERT("0.0.0.0/32", ipv4(0, 0, 0, 0), ipv4(0, 0, 0, 0));
RANGE_ASSERT("255.255.255.255/32", ipv4(255, 255, 255, 255), ipv4(255, 255, 255, 255));
}
+25
View File
@@ -0,0 +1,25 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
// ipv4_range_parse is a pure function, so this does not use test_fixture and
// the mock and logging setup that comes with it.
class TestCommandIp : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(TestCommandIp);
CPPUNIT_TEST(test_single_address);
CPPUNIT_TEST(test_explicit_range);
CPPUNIT_TEST(test_cidr);
CPPUNIT_TEST(test_cidr_zero_mask);
CPPUNIT_TEST(test_cidr_full_mask);
CPPUNIT_TEST_SUITE_END();
public:
void test_single_address();
void test_explicit_range();
void test_cidr();
void test_cidr_zero_mask();
void test_cidr_full_mask();
};