Skip to content

Commit 35e3efc

Browse files
committed
Distinguish source and destination sizes for safe_strcpy when using unterminated sources, fix rtrim
1 parent 25743fd commit 35e3efc

115 files changed

Lines changed: 846 additions & 847 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/onvif_motion_recording_example.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ void example_enable_motion_recording(void) {
3030
};
3131

3232
// Set codec and quality
33-
safe_strcpy(config.codec, "h264", sizeof(config.codec));
34-
safe_strcpy(config.quality, "high", sizeof(config.quality));
33+
safe_strcpy(config.codec, "h264", sizeof(config.codec), 0);
34+
safe_strcpy(config.quality, "high", sizeof(config.quality), 0);
3535

3636
// Enable for a stream
3737
const char *stream_name = "front_door";
@@ -140,8 +140,8 @@ void example_update_configuration(void) {
140140
.retention_days = 60 // Increased to 60 days
141141
};
142142

143-
safe_strcpy(new_config.codec, "h265", sizeof(new_config.codec));
144-
safe_strcpy(new_config.quality, "medium", sizeof(new_config.quality));
143+
safe_strcpy(new_config.codec, "h265", sizeof(new_config.codec), 0);
144+
safe_strcpy(new_config.quality, "medium", sizeof(new_config.quality), 0);
145145

146146
int result = update_motion_recording_config(stream_name, &new_config);
147147

@@ -199,8 +199,8 @@ void example_multiple_cameras(void) {
199199
.retention_days = 30
200200
};
201201

202-
safe_strcpy(config.codec, "h264", sizeof(config.codec));
203-
safe_strcpy(config.quality, "high", sizeof(config.quality));
202+
safe_strcpy(config.codec, "h264", sizeof(config.codec), 0);
203+
safe_strcpy(config.quality, "high", sizeof(config.quality), 0);
204204

205205
int result = enable_motion_recording(cameras[i], &config);
206206

include/utils/strings.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ char *safe_strdup(const char *str);
1818

1919
/**
2020
* Safe string copy with size checking. It is safe to pass an unterminated
21-
* string in `src`: the string will not be checked beyond `size` bytes.
21+
* string in `src`: the string will not be checked beyond `src_size` bytes.
2222
*
2323
* @param dest Destination buffer
2424
* @param src Source string
25-
* @param size Size of destination buffer
26-
* @return 0 on success, -1 on failure
25+
* @param dst_size Size of destination buffer
26+
* @param src_size Size of source buffer if not null-terminated
27+
* @return 0 if , -1 on failure
2728
*/
28-
int safe_strcpy(char *dest, const char *src, size_t size);
29+
int safe_strcpy(char *dest, const char *src, size_t dst_size, size_t src_size);
2930

3031
/**
3132
* Safe string concatenation with size checking
@@ -96,15 +97,19 @@ static inline const char *rtrim_pos(const char *input, size_t input_size) {
9697

9798
const char *end;
9899
if (input_size > 0) {
99-
end = (input + strnlen(input, input_size));
100+
end = (input + strnlen(input, input_size) - 1);
100101
} else {
101102
// If input_size is zero, use the unbounded strlen
102-
end = (input + strlen(input));
103+
end = (input + strlen(input) - 1);
103104
}
105+
// `end` will now point to the last non-null character. If the input is
106+
// empty, `end` will be (input-1), the character *before* the terminating
107+
// null.
104108
while (end > input && !isgraph((unsigned char)*end)) {
105109
end--;
106110
}
107-
// Point to the character after the last printable character.
111+
// Point to the character after the last printable character. If input was
112+
// empty, this will now point to the terminating null.
108113
return end + 1;
109114
}
110115

src/core/config.c

Lines changed: 47 additions & 47 deletions
Large diffs are not rendered by default.

src/core/daemon.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ static int check_running_daemon(const char *pid_file);
3333
int init_daemon(const char *pid_file) {
3434
// Store PID file path
3535
if (pid_file) {
36-
safe_strcpy(pid_file_path, pid_file, sizeof(pid_file_path));
36+
safe_strcpy(pid_file_path, pid_file, sizeof(pid_file_path), 0);
3737
}
3838

3939
// Check if daemon is already running
@@ -362,9 +362,9 @@ int stop_daemon(const char *pid_file) {
362362
char file_path[MAX_PATH_LENGTH];
363363

364364
if (pid_file) {
365-
safe_strcpy(file_path, pid_file, sizeof(file_path));
365+
safe_strcpy(file_path, pid_file, sizeof(file_path), 0);
366366
} else {
367-
safe_strcpy(file_path, pid_file_path, sizeof(file_path));
367+
safe_strcpy(file_path, pid_file_path, sizeof(file_path), 0);
368368
}
369369

370370
// Open and read PID file
@@ -511,9 +511,9 @@ int daemon_status(const char *pid_file) {
511511
char file_path[MAX_PATH_LENGTH];
512512

513513
if (pid_file) {
514-
safe_strcpy(file_path, pid_file, sizeof(file_path));
514+
safe_strcpy(file_path, pid_file, sizeof(file_path), 0);
515515
} else {
516-
safe_strcpy(file_path, pid_file_path, sizeof(file_path));
516+
safe_strcpy(file_path, pid_file_path, sizeof(file_path), 0);
517517
}
518518

519519
// First try to open the file with exclusive locking

src/core/logger.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ static __thread char tls_log_component[64] = {0};
5656
static __thread char tls_log_stream[128] = {0};
5757

5858
void log_set_thread_context(const char *component, const char *stream_name) {
59-
safe_strcpy(tls_log_component, component ? component : "", sizeof(tls_log_component));
60-
safe_strcpy(tls_log_stream, stream_name ? stream_name : "", sizeof(tls_log_stream));
59+
safe_strcpy(tls_log_component, component ? component : "", sizeof(tls_log_component), 0);
60+
safe_strcpy(tls_log_stream, stream_name ? stream_name : "", sizeof(tls_log_stream), 0);
6161
}
6262

6363
void log_clear_thread_context(void) {
@@ -262,7 +262,7 @@ int set_log_file(const char *filename) {
262262
}
263263

264264
// Store filename for potential log rotation
265-
safe_strcpy(logger.log_filename, filename, sizeof(logger.log_filename));
265+
safe_strcpy(logger.log_filename, filename, sizeof(logger.log_filename), 0);
266266

267267
pthread_mutex_unlock(&logger.mutex);
268268

@@ -603,7 +603,7 @@ int enable_syslog(const char *ident, int facility) {
603603
}
604604

605605
// Store the identifier
606-
safe_strcpy(logger.syslog_ident, ident, sizeof(logger.syslog_ident));
606+
safe_strcpy(logger.syslog_ident, ident, sizeof(logger.syslog_ident), 0);
607607

608608
// Open syslog connection
609609
// LOG_PID: include PID with each message

src/core/logger_json.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ int init_json_logger(const char *filename) {
121121
}
122122

123123
// Store filename for potential log rotation
124-
safe_strcpy(json_logger.log_filename, filename, sizeof(json_logger.log_filename));
124+
safe_strcpy(json_logger.log_filename, filename, sizeof(json_logger.log_filename), 0);
125125

126126
json_logger.initialized = 1;
127127

src/core/main.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ static int create_pid_file(const char *pid_file) {
361361
if (last_slash) {
362362
char dir_path[MAX_PATH_LENGTH];
363363
size_t dir_len = (size_t)(last_slash - pid_file);
364-
safe_strcpy(dir_path, pid_file, dir_len);
364+
safe_strcpy(dir_path, pid_file, MAX_PATH_LENGTH, dir_len);
365365

366366
// Create directory if it doesn't exist
367367
struct stat st;
@@ -549,7 +549,7 @@ int main(int argc, char *argv[]) {
549549
} else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) {
550550
if (i + 1 < argc) {
551551
// Set config file path
552-
safe_strcpy(custom_config_path, argv[i+1], MAX_PATH_LENGTH);
552+
safe_strcpy(custom_config_path, argv[i+1], MAX_PATH_LENGTH, 0);
553553
i++;
554554
} else {
555555
log_error("Missing config file path");
@@ -756,7 +756,7 @@ int main(int argc, char *argv[]) {
756756

757757
// Create parent directory for symlink if needed
758758
char parent_dir[MAX_PATH_LENGTH];
759-
safe_strcpy(parent_dir, config.web_root, sizeof(parent_dir));
759+
safe_strcpy(parent_dir, config.web_root, sizeof(parent_dir), 0);
760760
char *last_slash = strrchr(parent_dir, '/');
761761
if (last_slash) {
762762
*last_slash = '\0';
@@ -771,7 +771,7 @@ int main(int argc, char *argv[]) {
771771
config.web_root, storage_web_path, strerror(errno));
772772

773773
// Fall back to using the storage path directly
774-
safe_strcpy(config.web_root, storage_web_path, MAX_PATH_LENGTH);
774+
safe_strcpy(config.web_root, storage_web_path, MAX_PATH_LENGTH, 0);
775775
log_warn("Using storage path directly for web root: %s", config.web_root);
776776
} else {
777777
log_info("Created symlink from %s to %s", config.web_root, storage_web_path);
@@ -922,13 +922,13 @@ int main(int argc, char *argv[]) {
922922
};
923923

924924
// Set CORS allowed origins, methods, and headers
925-
safe_strcpy(server_config.allowed_origins, "*", sizeof(server_config.allowed_origins));
926-
safe_strcpy(server_config.allowed_methods, "GET, POST, PUT, DELETE, OPTIONS", sizeof(server_config.allowed_methods));
927-
safe_strcpy(server_config.allowed_headers, "Content-Type, Authorization", sizeof(server_config.allowed_headers));
925+
safe_strcpy(server_config.allowed_origins, "*", sizeof(server_config.allowed_origins), 0);
926+
safe_strcpy(server_config.allowed_methods, "GET, POST, PUT, DELETE, OPTIONS", sizeof(server_config.allowed_methods), 0);
927+
safe_strcpy(server_config.allowed_headers, "Content-Type, Authorization", sizeof(server_config.allowed_headers), 0);
928928

929929
if (config.web_auth_enabled) {
930-
safe_strcpy(server_config.username, config.web_username, sizeof(server_config.username));
931-
safe_strcpy(server_config.password, config.web_password, sizeof(server_config.password));
930+
safe_strcpy(server_config.username, config.web_username, sizeof(server_config.username), 0);
931+
safe_strcpy(server_config.password, config.web_password, sizeof(server_config.password), 0);
932932
}
933933

934934
// Initialize HTTP server (libuv + llhttp)
@@ -1016,7 +1016,7 @@ int main(int argc, char *argv[]) {
10161016

10171017
if (is_api_based) {
10181018
// For API-based or built-in detection (motion, onvif), use the model string as-is
1019-
safe_strcpy(model_path, config.streams[i].detection_model, MAX_PATH_LENGTH);
1019+
safe_strcpy(model_path, config.streams[i].detection_model, MAX_PATH_LENGTH, 0);
10201020
log_info("Using built-in/API detection for stream %s: %s",
10211021
config.streams[i].name, model_path);
10221022
} else if (config.streams[i].detection_model[0] != '/') {
@@ -1046,7 +1046,7 @@ int main(int argc, char *argv[]) {
10461046
}
10471047
} else {
10481048
// Absolute path
1049-
safe_strcpy(model_path, config.streams[i].detection_model, MAX_PATH_LENGTH);
1049+
safe_strcpy(model_path, config.streams[i].detection_model, MAX_PATH_LENGTH, 0);
10501050

10511051
// Check if file exists
10521052
FILE *model_file = fopen(model_path, "r");

src/core/mqtt_client.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ void mqtt_set_motion_state(const char *stream_name, const detection_result_t *re
759759
if (!state && num_motion_states < MAX_MOTION_STREAMS) {
760760
state = &motion_states[num_motion_states++];
761761
memset(state, 0, sizeof(*state));
762-
safe_strcpy(state->stream_name, stream_name, sizeof(state->stream_name));
762+
safe_strcpy(state->stream_name, stream_name, sizeof(state->stream_name), 0);
763763
}
764764

765765
if (!state) {
@@ -793,8 +793,7 @@ void mqtt_set_motion_state(const char *stream_name, const detection_result_t *re
793793
}
794794
if (label_idx < 0 && state->num_labels < 32) {
795795
label_idx = state->num_labels++;
796-
safe_strcpy(state->object_labels[label_idx], label,
797-
sizeof(state->object_labels[0]) - 1);
796+
safe_strcpy(state->object_labels[label_idx], label, sizeof(state->object_labels[0]), 0);
798797
}
799798
if (label_idx >= 0) {
800799
state->object_counts[label_idx]++;
@@ -921,7 +920,7 @@ static void *ha_motion_thread_func(void *arg) {
921920

922921
motion_states[i].motion_active = false;
923922
char stream_name[256];
924-
safe_strcpy(stream_name, motion_states[i].stream_name, sizeof(stream_name));
923+
safe_strcpy(stream_name, motion_states[i].stream_name, sizeof(stream_name), 0);
925924

926925
char safe_name[256];
927926
sanitize_stream_name(stream_name, safe_name, sizeof(safe_name));

src/core/shutdown_coordinator.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ int register_component(const char *name, component_type_t type, void *context, i
109109

110110
// Initialize the component slot
111111
component_info_t *component = &g_coordinator.components[slot];
112-
safe_strcpy(component->name, name, sizeof(component->name));
112+
safe_strcpy(component->name, name, sizeof(component->name), 0);
113113
component->type = type;
114114
atomic_store(&component->state, COMPONENT_RUNNING);
115115
component->context = context;

src/database/db_auth.c

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ static int parse_cidr_entry(const char *cidr, int *family, unsigned char *networ
114114
if (strlen(cidr) >= sizeof(entry)) {
115115
return -1;
116116
}
117-
safe_strcpy(entry, cidr, sizeof(entry));
117+
safe_strcpy(entry, cidr, sizeof(entry), 0);
118118

119119
char *trimmed = trim_ascii_whitespace(entry);
120120
if (!trimmed || trimmed[0] == '\0') {
@@ -194,7 +194,7 @@ static int normalize_allowed_login_cidrs(const char *allowed_login_cidrs,
194194
}
195195

196196
char input[USER_ALLOWED_LOGIN_CIDRS_MAX];
197-
safe_strcpy(input, allowed_login_cidrs, sizeof(input));
197+
safe_strcpy(input, allowed_login_cidrs, sizeof(input), 0);
198198

199199
char *saveptr = NULL;
200200
for (char *token = strtok_r(input, ",\n", &saveptr);
@@ -309,18 +309,18 @@ static void populate_user_from_stmt(sqlite3_stmt *stmt, user_t *user) {
309309
memset(user, 0, sizeof(*user));
310310

311311
user->id = sqlite3_column_int64(stmt, 0);
312-
safe_strcpy(user->username, (const char *)sqlite3_column_text(stmt, 1), sizeof(user->username));
312+
safe_strcpy(user->username, (const char *)sqlite3_column_text(stmt, 1), sizeof(user->username), 0);
313313

314314
const char *email = (const char *)sqlite3_column_text(stmt, 2);
315315
if (email) {
316-
safe_strcpy(user->email, email, sizeof(user->email));
316+
safe_strcpy(user->email, email, sizeof(user->email), 0);
317317
}
318318

319319
user->role = (user_role_t)sqlite3_column_int(stmt, 3);
320320

321321
const char *api_key = (const char *)sqlite3_column_text(stmt, 4);
322322
if (api_key) {
323-
safe_strcpy(user->api_key, api_key, sizeof(user->api_key));
323+
safe_strcpy(user->api_key, api_key, sizeof(user->api_key), 0);
324324
}
325325

326326
user->created_at = sqlite3_column_int64(stmt, 5);
@@ -332,13 +332,13 @@ static void populate_user_from_stmt(sqlite3_stmt *stmt, user_t *user) {
332332

333333
const char *allowed_tags = (const char *)sqlite3_column_text(stmt, 11);
334334
if (allowed_tags && allowed_tags[0] != '\0') {
335-
safe_strcpy(user->allowed_tags, allowed_tags, sizeof(user->allowed_tags));
335+
safe_strcpy(user->allowed_tags, allowed_tags, sizeof(user->allowed_tags), 0);
336336
user->has_tag_restriction = true;
337337
}
338338

339339
const char *allowed_login_cidrs = (const char *)sqlite3_column_text(stmt, 12);
340340
if (allowed_login_cidrs && allowed_login_cidrs[0] != '\0') {
341-
safe_strcpy(user->allowed_login_cidrs, allowed_login_cidrs, sizeof(user->allowed_login_cidrs));
341+
safe_strcpy(user->allowed_login_cidrs, allowed_login_cidrs, sizeof(user->allowed_login_cidrs), 0);
342342
user->has_login_cidr_restriction = true;
343343
}
344344
}
@@ -1864,9 +1864,9 @@ int db_auth_list_user_sessions(int64_t user_id, session_t *sessions, int max_cou
18641864
const char *token = (const char *)sqlite3_column_text(stmt, 2);
18651865
const char *ip = (const char *)sqlite3_column_text(stmt, 7);
18661866
const char *ua = (const char *)sqlite3_column_text(stmt, 8);
1867-
if (token) safe_strcpy(session->token, token, sizeof(session->token));
1868-
if (ip) safe_strcpy(session->ip_address, ip, sizeof(session->ip_address));
1869-
if (ua) safe_strcpy(session->user_agent, ua, sizeof(session->user_agent));
1867+
if (token) safe_strcpy(session->token, token, sizeof(session->token), 0);
1868+
if (ip) safe_strcpy(session->ip_address, ip, sizeof(session->ip_address), 0);
1869+
if (ua) safe_strcpy(session->user_agent, ua, sizeof(session->user_agent), 0);
18701870
session->created_at = sqlite3_column_int64(stmt, 3);
18711871
session->last_activity_at = sqlite3_column_int64(stmt, 4);
18721872
session->idle_expires_at = sqlite3_column_int64(stmt, 5);
@@ -2079,8 +2079,8 @@ int db_auth_list_trusted_devices(int64_t user_id, trusted_device_t *devices, int
20792079
device->user_id = sqlite3_column_int64(stmt, 1);
20802080
const char *ip = (const char *)sqlite3_column_text(stmt, 5);
20812081
const char *ua = (const char *)sqlite3_column_text(stmt, 6);
2082-
if (ip) safe_strcpy(device->ip_address, ip, sizeof(device->ip_address));
2083-
if (ua) safe_strcpy(device->user_agent, ua, sizeof(device->user_agent));
2082+
if (ip) safe_strcpy(device->ip_address, ip, sizeof(device->ip_address), 0);
2083+
if (ua) safe_strcpy(device->user_agent, ua, sizeof(device->user_agent), 0);
20842084
device->created_at = sqlite3_column_int64(stmt, 2);
20852085
device->last_used_at = sqlite3_column_int64(stmt, 3);
20862086
device->expires_at = sqlite3_column_int64(stmt, 4);
@@ -2188,7 +2188,7 @@ int db_auth_get_totp_info(int64_t user_id, char *secret, size_t secret_size, boo
21882188

21892189
const char *db_secret = (const char *)sqlite3_column_text(stmt, 0);
21902190
if (db_secret && db_secret[0] != '\0') {
2191-
safe_strcpy(secret, db_secret, secret_size);
2191+
safe_strcpy(secret, db_secret, secret_size, 0);
21922192
}
21932193

21942194
*enabled = sqlite3_column_int(stmt, 1) != 0;
@@ -2398,7 +2398,7 @@ bool db_auth_ip_allowed_for_user(const user_t *user, const char *client_ip) {
23982398
}
23992399

24002400
char cidr_list[USER_ALLOWED_LOGIN_CIDRS_MAX];
2401-
safe_strcpy(cidr_list, user->allowed_login_cidrs, USER_ALLOWED_LOGIN_CIDRS_MAX);
2401+
safe_strcpy(cidr_list, user->allowed_login_cidrs, USER_ALLOWED_LOGIN_CIDRS_MAX, 0);
24022402

24032403
char *saveptr = NULL;
24042404
for (char *token = strtok_r(cidr_list, ",\n", &saveptr);
@@ -2434,7 +2434,7 @@ bool db_auth_stream_allowed_for_user(const user_t *user, const char *stream_tags
24342434

24352435
// Tokenize stream_tags and check each against user's allowed_tags
24362436
char stream_copy[256];
2437-
safe_strcpy(stream_copy, stream_tags, sizeof(stream_copy));
2437+
safe_strcpy(stream_copy, stream_tags, sizeof(stream_copy), 0);
24382438

24392439
char *saveptr = NULL;
24402440
char *token = strtok_r(stream_copy, ",", &saveptr);

0 commit comments

Comments
 (0)