mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-23 02:55:44 -04:00
fix: do not fault in the shm time accessors before connect() has mapped
Monitor::connect() returns false with shared_data still null on every one of
its failure paths: the mmap file cannot be opened (wrong ownership, e.g.
after a package upgrade), fstat fails, ftruncate cannot grow it (/dev/shm out
of space -- a container with the default 64MB tmpfs hits this quickly, since
one 720x480 monitor with 10 buffers already asks for ~20MB and a 1080x720 one
asks for ~62MB), or mmap itself fails.
zmc's startup loop reacts by retrying:
while (!monitor->connect() and !zm_terminate) {
Warning("Couldn't connect to monitor %d", monitor->Id());
monitor->SetHeartbeatTime(std::chrono::system_clock::now());
sleep(1);
}
so the first thing it does after a failed connect is write through the null
pointer. zmc dies with SIGSEGV at address 0x80 instead of retrying, which
presents as a monitor that will not start and a capture daemon that keeps
crashing. The accessors on either side of these already guard with
`if (shared_data && shared_data->valid)`.
Reproduced under AddressSanitizer both as a unit test and in a full zmc run
against an mmap file the process could not open; the new test faults at
zm_monitor.h:925 with rax=0x80 before the change and passes after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBHBB95RBX7D9p8ge2WDZb
This commit is contained in:
1 parent
588713abc8
commit
a550545e45
3 files changed
+94
-2
No files matched your search
+16
-2
@@ -919,9 +919,23 @@ class Monitor : public std::enable_shared_from_this<Monitor> {
|
||||
void ForceAlarmOff();
|
||||
void CancelForced();
|
||||
TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; }
|
||||
SystemTimePoint GetStartupTime() const { return std::chrono::system_clock::from_time_t(shared_data->startup_time); }
|
||||
void SetStartupTime(SystemTimePoint time) { shared_data->startup_time = std::chrono::system_clock::to_time_t(time); }
|
||||
// shared_data is null until connect() maps the shm segment, and connect()
|
||||
// fails with it still null whenever the mmap file cannot be opened, grown or
|
||||
// mapped -- wrong ownership, or /dev/shm too small for the requested buffers.
|
||||
// zmc's startup loop calls SetHeartbeatTime() on every failed retry, so
|
||||
// without these guards a monitor that cannot get its shm takes zmc down with
|
||||
// SIGSEGV instead of retrying. The accessors around these already guard the
|
||||
// same way.
|
||||
SystemTimePoint GetStartupTime() const {
|
||||
if (!shared_data) return SystemTimePoint();
|
||||
return std::chrono::system_clock::from_time_t(shared_data->startup_time);
|
||||
}
|
||||
void SetStartupTime(SystemTimePoint time) {
|
||||
if (!shared_data) return;
|
||||
shared_data->startup_time = std::chrono::system_clock::to_time_t(time);
|
||||
}
|
||||
void SetHeartbeatTime(SystemTimePoint time) {
|
||||
if (!shared_data) return;
|
||||
shared_data->heartbeat_time = std::chrono::system_clock::to_time_t(time);
|
||||
}
|
||||
void get_ref_image();
|
||||
|
||||
@@ -19,6 +19,7 @@ set(TEST_SOURCES
|
||||
zm_db_contention_live.cpp
|
||||
zm_event_hardlink.cpp
|
||||
zm_font.cpp
|
||||
zm_monitor_shm.cpp
|
||||
zm_monitorstream.cpp
|
||||
zm_packetqueue.cpp
|
||||
zm_onvif_auth_error.cpp
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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_config.h"
|
||||
#include "zm_monitor.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
// Monitor::connect() returns false with shared_data still null on every one of
|
||||
// its failure paths: the mmap file cannot be opened (wrong ownership after a
|
||||
// package upgrade), fstat fails, ftruncate cannot grow it (/dev/shm out of
|
||||
// space, which a container with the default 64MB tmpfs hits quickly -- one
|
||||
// 720x480 monitor with 10 buffers already asks for ~20MB), or mmap itself
|
||||
// fails.
|
||||
//
|
||||
// zmc's startup loop reacts to that by retrying:
|
||||
//
|
||||
// while (!monitor->connect() and !zm_terminate) {
|
||||
// Warning("Couldn't connect to monitor %d", monitor->Id());
|
||||
// monitor->SetHeartbeatTime(std::chrono::system_clock::now());
|
||||
// sleep(1);
|
||||
// }
|
||||
//
|
||||
// so the very first thing it does after a failed connect is write through the
|
||||
// null pointer, and zmc dies with SIGSEGV at address 0x80 instead of retrying.
|
||||
// The surrounding accessors in zm_monitor.h already guard with
|
||||
// `if (shared_data && shared_data->valid)`; these did not.
|
||||
namespace {
|
||||
|
||||
void EnsureConfig() {
|
||||
if (!config.font_file_location) config.font_file_location = "";
|
||||
if (!config.event_close_mode) config.event_close_mode = "idle";
|
||||
}
|
||||
|
||||
// A Monitor that has never connected, so shared_data is null -- exactly the
|
||||
// state zmc is in while its retry loop spins.
|
||||
class UnconnectedMonitor : public Monitor {
|
||||
public:
|
||||
UnconnectedMonitor() : Monitor() {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Monitor shm time accessors are safe before connect()", "[Monitor]") {
|
||||
EnsureConfig();
|
||||
UnconnectedMonitor monitor;
|
||||
|
||||
REQUIRE_FALSE(monitor.isConnected());
|
||||
|
||||
SECTION("SetHeartbeatTime does not fault (zmc's connect retry loop)") {
|
||||
REQUIRE_NOTHROW(monitor.SetHeartbeatTime(std::chrono::system_clock::now()));
|
||||
}
|
||||
|
||||
SECTION("SetStartupTime does not fault") {
|
||||
REQUIRE_NOTHROW(monitor.SetStartupTime(std::chrono::system_clock::now()));
|
||||
}
|
||||
|
||||
SECTION("GetStartupTime does not fault") {
|
||||
REQUIRE_NOTHROW(monitor.GetStartupTime());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user