mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-12 11:43:13 -04:00
RemoteCameraHttp read the camera's Content-Length with atoi() and then asked ReadData() for that many bytes, which passes the count straight to Buffer::read_into(). Buffer::expand() worked in int: once the allocation passed INT_MAX the spare space went negative, it grew again, and mAllocation + count wrapped to a small value, so the following memcpy or read() wrote the camera's data past a small heap block. Whoever answers the camera's HTTP request, often on a plain-HTTP link, could crash zmc in a loop or corrupt its heap. - RemoteCameraHttp::ParseContentLength() replaces the four atoi() calls. It refuses a length that is not a number or is larger than twice the monitor's raw frame size plus one network buffer (capped at 1 GiB), and GetResponse() fails the response instead of buffering it. - Buffer::expand() does its arithmetic in size_t, grows to exactly what is needed, and refuses to hold more than INT_MAX bytes, since read_into() returns the count as an int. - Buffer::read_into() refuses a read that does not fit after expand() rather than reading past the allocation. tests/zm_buffer.cpp covers reads through a pipe, an oversized read, data kept across a shift and a grow, and the Content-Length bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
3.8 KiB
C++
101 lines
3.8 KiB
C++
/*
|
|
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program 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 General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "zm_catch2.h"
|
|
|
|
#include "zm_buffer.h"
|
|
#include "zm_remote_camera_http.h"
|
|
|
|
#include <climits>
|
|
#include <cstring>
|
|
#include <unistd.h>
|
|
|
|
// A Remote/HTTP camera's Content-Length decides how much zmc reads into a Buffer. Buffer did
|
|
// its size arithmetic in int, so a length near 2^31 wrapped the allocation to a small block
|
|
// and the read or the copy ran past it. refs GHSA-x959-xrc9-89p8
|
|
|
|
namespace {
|
|
|
|
struct Pipe {
|
|
int fds[2] = {-1, -1};
|
|
Pipe() { REQUIRE(pipe(fds) == 0); }
|
|
~Pipe() {
|
|
if (fds[0] >= 0) close(fds[0]);
|
|
if (fds[1] >= 0) close(fds[1]);
|
|
}
|
|
void Write(const std::string &s) { REQUIRE(write(fds[1], s.data(), s.size()) == static_cast<ssize_t>(s.size())); }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Buffer::read_into reads what arrives", "[Buffer]") {
|
|
Pipe p;
|
|
p.Write("hello");
|
|
Buffer buffer(4);
|
|
REQUIRE(buffer.read_into(p.fds[0], 16) == 5);
|
|
REQUIRE(buffer.size() == 5);
|
|
REQUIRE(std::memcmp(buffer.head(), "hello", 5) == 0);
|
|
}
|
|
|
|
TEST_CASE("Buffer::read_into refuses a read the buffer cannot hold", "[Buffer]") {
|
|
Pipe p;
|
|
p.Write("hello");
|
|
Buffer buffer(16);
|
|
// More than an int can count: before the fix this allocated about 2 GiB and then read.
|
|
REQUIRE(buffer.read_into(p.fds[0], static_cast<unsigned int>(INT_MAX) + 1u) < 0);
|
|
REQUIRE(buffer.size() == 0);
|
|
// The data is still there for a sensible read.
|
|
REQUIRE(buffer.read_into(p.fds[0], 16) == 5);
|
|
}
|
|
|
|
TEST_CASE("Buffer::expand keeps the data when it moves or grows", "[Buffer]") {
|
|
Buffer buffer(8);
|
|
buffer.append("abcdefgh", 8);
|
|
buffer.consume(6); // "gh" left, with 6 bytes of head space
|
|
|
|
// Fits once the data is moved to the front of the allocation.
|
|
buffer.expand(5);
|
|
REQUIRE(buffer.size() == 2);
|
|
REQUIRE(std::memcmp(buffer.head(), "gh", 2) == 0);
|
|
REQUIRE(buffer.tail() == buffer.head() + 2);
|
|
|
|
// Needs a bigger allocation.
|
|
buffer.expand(100);
|
|
REQUIRE(buffer.size() == 2);
|
|
REQUIRE(std::memcmp(buffer.head(), "gh", 2) == 0);
|
|
buffer.append(std::string(100, 'x').c_str(), 100);
|
|
REQUIRE(buffer.size() == 102);
|
|
}
|
|
|
|
TEST_CASE("RemoteCameraHttp::ParseContentLength bounds the announced length", "[RemoteCameraHttp]") {
|
|
const unsigned long long image_size = 640 * 480 * 4;
|
|
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("1000", image_size) == 1000);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("0", image_size) == 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("1000\r\n", image_size) == 1000);
|
|
// A frame can't be bigger than twice its raw size.
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("2457600", image_size) == 2457600);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("50000000", image_size) < 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("2147483647", image_size) < 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("99999999999999999999", image_size) < 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("-5", image_size) < 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("abc", image_size) < 0);
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("", image_size) < 0);
|
|
// A huge configured frame still yields a length an int can hold.
|
|
REQUIRE(RemoteCameraHttp::ParseContentLength("2147483647", 4000000000ull) < 0);
|
|
}
|