What changed, and why it matters
This commit removes the use of standard string-formatting functions (like snprintf and Rust's format!) from the bootloader and related low-level code, replacing them with smaller, fixed-size string builders. The stated goal is to reduce binary size ('bloat') in the production bootloader, not to fix a known security bug. The change also adds a CI check that rejects any future use of these formatting symbols in production bootloader builds. While removing complex formatting libraries from a bootloader is generally good defensive practice, the commit itself does not describe or demonstrate any exploitable vulnerability.
Treat as a hardening and code-quality improvement rather than an urgent security fix. Review the new fixed-size formatters for off-by-one and null-termination correctness, ensure the CI symbol patterns cover all relevant formatting entry points, and continue to monitor for any future reintroduction of stdio/Rust formatting into bootloader builds.
Security signals we found
Removal of stdio formatting (snprintf family) from bootloader code
Removal of Rust format!/write! formatting from bootloader-linked code
Addition of CI gate rejecting stdio and Rust formatting symbols in production bootloader ELFs
Replacement with fixed-size, bounded string/integer formatting helpers
New util_strlcpy helper for safe string copy with explicit bounds
No vendor claim of vulnerability or CVE in commit message
Evidence from the diff
The patch replaces snprintf-based formatting in the BitBox02 bootloader with custom fixed-length helpers (bootloader_format_*) and a new Rust rust_format_uint. It also removes snprintf usage from UGUI line slicing, screen debug printing, and the BLE random-name generator. A CI script now scans production bootloader ELFs for stdio and Rust formatting symbols and fails the build if found. The changes are accompanied by unit tests for the new helpers. There is no direct evidence in the commit of a memory-safety bug, format-string vulnerability, or other security defect; the rationale given is binary-size reduction.
Changed components
src/bootloader/bootloader_format.csrc/bootloader/bootloader_format.hsrc/rust/util/src/bytes.rssrc/ui/ugui/ugui.csrc/screen.csrc/memory/memory_shared.csrc/util.csrc/util.h.ci/check-unwanted-symbols.github/workflows/ci-common.ymlInspect captured patch +298 / −63
diff --git a/.ci/check-unwanted-symbols b/.ci/check-unwanted-symbols
index 3f5e80d..058ca42 100755
--- a/.ci/check-unwanted-symbols
+++ b/.ci/check-unwanted-symbols
@@ -2,20 +2,18 @@
set -euo pipefail
-elf=build/bin/firmware.elf
-if [[ ! -f "$elf" ]]; then
- echo "ELF file not found: $elf" >&2
- exit 2
-fi
-
# Disallow symbols in the final linked ELF that indicate expensive code paths.
-symbols=$(arm-none-eabi-nm -C "$elf")
failed=0
+checked=0
check_symbols() {
- local name=$1
- local pattern=$2
- shift 2
+ local elf=$1
+ local name=$2
+ local pattern=$3
+ shift 3
+
+ local symbols
+ symbols=$(arm-none-eabi-nm -C "$elf")
local matches
matches=$(grep -E "$pattern" <<<"$symbols" || true)
@@ -23,7 +21,7 @@ check_symbols() {
return
fi
- echo "Found unwanted symbols for: $name" >&2
+ echo "Found unwanted symbols for: $name in $elf" >&2
echo "$matches" | sed -n '1,20p' >&2
for line in "$@"; do
echo "$line" >&2
@@ -32,28 +30,76 @@ check_symbols() {
failed=1
}
-check_symbols \
- "Rust float formatting" \
- "float_to_decimal_common_shortest" \
- "Rust fmt float formatting like {:.1} adds significant binary bloat." \
- "Use integer arithmetic and format the resulting integer parts explicitly."
-
-check_symbols \
- "strftime" \
- "(^|[[:space:]])strftime($|[[:space:]])" \
- "strftime adds significant binary bloat." \
- "Use custom formatting like in format_datetime()."
-
-check_symbols \
- "sha2::Sha512" \
- "sha26sha512|sha2::sha512" \
- "sha2::Sha512 adds significant binary bloat." \
- "Only use it if there is no other sha512 implementation available that is smaller."
-
-check_symbols \
- "software f32 arithmetic helpers" \
- "(__aeabi_f(add|sub|mul|div)|__(add|sub|mul|div|neg)sf3|compiler_builtins::float::(add|sub|mul|div)::.*f32)" \
- "Software f32 arithmetic helpers add significant binary bloat." \
- "Use integer arithmetic in firmware code instead."
+check_firmware() {
+ local elf=$1
+ checked=1
+
+ check_symbols \
+ "$elf" \
+ "Rust float formatting" \
+ "float_to_decimal_common_shortest" \
+ "Rust fmt float formatting like {:.1} adds significant binary bloat." \
+ "Use integer arithmetic and format the resulting integer parts explicitly."
+
+ check_symbols \
+ "$elf" \
+ "strftime" \
+ "(^|[[:space:]])strftime($|[[:space:]])" \
+ "strftime adds significant binary bloat." \
+ "Use custom formatting like in format_datetime()."
+
+ check_symbols \
+ "$elf" \
+ "sha2::Sha512" \
+ "sha26sha512|sha2::sha512" \
+ "sha2::Sha512 adds significant binary bloat." \
+ "Only use it if there is no other sha512 implementation available that is smaller."
+
+ check_symbols \
+ "$elf" \
+ "software f32 arithmetic helpers" \
+ "(__aeabi_f(add|sub|mul|div)|__(add|sub|mul|div|neg)sf3|compiler_builtins::float::(add|sub|mul|div)::.*f32)" \
+ "Software f32 arithmetic helpers add significant binary bloat." \
+ "Use integer arithmetic in firmware code instead."
+}
+
+check_production_bootloader() {
+ local elf=$1
+ checked=1
+
+ local printf_symbol_pattern
+ printf_symbol_pattern='(^|[[:space:]])(_?(asiprintf|asnprintf|asprintf|fprintf|iprintf|printf|siprintf|sniprintf|snprintf|sprintf|vasiprintf|vasnprintf|vasprintf|vfprintf|vfiprintf|vprintf|vsiprintf|vsniprintf|vsnprintf|vsprintf)(_r)?|_printf_common|_printf_i|_svfprintf_r|_svfiprintf_r|__sprint_r|__ssputs_r)([.$[:space:]]|$)'
+ check_symbols \
+ "$elf" \
+ "stdio formatting" \
+ "$printf_symbol_pattern" \
+ "snprintf/printf pulls in significant bootloader bloat." \
+ "Avoid stdio formatting in production bootloaders; use fixed-format helpers instead."
+
+ local rust_fmt_symbol_pattern
+ rust_fmt_symbol_pattern='(^|[[:space:]])(alloc::fmt::|core::fmt::|<alloc::string::String as core::fmt::Write>)'
+ check_symbols \
+ "$elf" \
+ "Rust formatting" \
+ "$rust_fmt_symbol_pattern" \
+ "Rust format!/write! formatting pulls in significant bootloader bloat." \
+ "Avoid Rust formatting in production bootloaders; use fixed-format helpers instead."
+}
+
+firmware_elf=build/bin/firmware.elf
+if [[ -f "$firmware_elf" ]]; then
+ check_firmware "$firmware_elf"
+fi
+
+shopt -s nullglob
+for bootloader_elf in build/bin/*-bl-*-production.elf; do
+ check_production_bootloader "$bootloader_elf"
+done
+shopt -u nullglob
+
+if [[ "$checked" == 0 ]]; then
+ echo "No checked ELF files found in build/bin" >&2
+ exit 2
+fi
exit "$failed"
diff --git a/.github/workflows/ci-common.yml b/.github/workflows/ci-common.yml
index efdb361..8663c95 100644
--- a/.github/workflows/ci-common.yml
+++ b/.github/workflows/ci-common.yml
@@ -242,7 +242,7 @@ jobs:
run: make -j$(($(nproc)+1)) ${{ matrix.target }}
- name: Check unwanted symbols
- if: matrix.target == 'firmware' && !cancelled()
+ if: (matrix.target == 'firmware' || (startsWith(matrix.target, 'bootloader') && endsWith(matrix.target, 'production'))) && !cancelled()
run: ./.ci/check-unwanted-symbols
- name: Print hashes
diff --git a/src/bootloader/bootloader_format.c b/src/bootloader/bootloader_format.c
index f743e4f..5e407b7 100644
--- a/src/bootloader/bootloader_format.c
+++ b/src/bootloader/bootloader_format.c
@@ -2,36 +2,53 @@
#include "bootloader/bootloader_format.h"
-#include <stdio.h>
+#include <rust/rust.h>
+#include <string.h>
+#include <utils_assert.h>
void bootloader_format_pairing_code(char* out, size_t out_len, uint32_t pairing_code)
{
- snprintf(out, out_len, "%06u", (unsigned)pairing_code);
+ ASSERT(out_len >= sizeof("000000"));
+ rust_format_uint(rust_util_bytes_mut((uint8_t*)out, out_len), pairing_code, 6, '0');
}
void bootloader_format_progress(char* out, size_t out_len, float progress)
{
- snprintf(out, out_len, "%2d%%", (int)(100 * progress));
+ ASSERT(out_len >= sizeof("100%"));
+ size_t out_pos = rust_format_uint(
+ rust_util_bytes_mut((uint8_t*)out, out_len - 1), (uint32_t)(100 * progress), 2, ' ');
+ out[out_pos++] = '%';
+ out[out_pos] = '\0';
}
void bootloader_format_hash_multiline(char* out, size_t out_len, const char* hash_hex)
{
- snprintf(
- out,
- out_len,
- "%.16s\n%.16s\n%.16s\n%.16s",
- &hash_hex[0],
- &hash_hex[16],
- &hash_hex[32],
- &hash_hex[48]);
+ ASSERT(out_len >= 4 * 16 + 3 + 1);
+ (void)out_len;
+ for (size_t i = 0; i < 4; i++) {
+ memcpy(&out[i * 17], &hash_hex[i * 16], 16);
+ out[i * 17 + 16] = i == 3 ? '\0' : '\n';
+ }
}
void bootloader_format_timer(char* out, size_t out_len, uint8_t seconds)
{
- snprintf(out, out_len, "%ds", seconds);
+ ASSERT(out_len >= sizeof("99s"));
+ size_t out_pos =
+ rust_format_uint(rust_util_bytes_mut((uint8_t*)out, out_len - 1), seconds, 1, '0');
+ out[out_pos++] = 's';
+ out[out_pos] = '\0';
}
void bootloader_format_unknown_command(char* out, size_t out_len, uint8_t command)
{
- snprintf(out, out_len, "Command: %u unknown", command);
+ const char prefix[] = "Command: ";
+ const char suffix[] = " unknown";
+
+ ASSERT(out_len >= sizeof("Command: 255 unknown"));
+ size_t out_pos = sizeof(prefix) - 1;
+ memcpy(out, prefix, out_pos);
+ out_pos += rust_format_uint(
+ rust_util_bytes_mut((uint8_t*)&out[out_pos], out_len - out_pos), command, 1, '0');
+ memcpy(&out[out_pos], suffix, sizeof(suffix));
}
diff --git a/src/bootloader/bootloader_format.h b/src/bootloader/bootloader_format.h
index 8733707..048b120 100644
--- a/src/bootloader/bootloader_format.h
+++ b/src/bootloader/bootloader_format.h
@@ -6,10 +6,40 @@
#include <stddef.h>
#include <stdint.h>
+/**
+ * Format the six-digit BLE pairing code.
+ *
+ * pairing_code must be at most 999999. out_len must be at least
+ * sizeof("000000").
+ */
void bootloader_format_pairing_code(char* out, size_t out_len, uint32_t pairing_code);
+
+/**
+ * Format progress as a percentage.
+ *
+ * progress must be between 0 and 1. out_len must be at least sizeof("100%").
+ */
void bootloader_format_progress(char* out, size_t out_len, float progress);
+
+/**
+ * Format a 64-character hex hash as four newline-separated 16-character lines.
+ *
+ * out_len must be at least 4 * 16 + 3 + 1.
+ */
void bootloader_format_hash_multiline(char* out, size_t out_len, const char* hash_hex);
+
+/**
+ * Format a timer value with an "s" suffix.
+ *
+ * seconds must be at most 99. out_len must be at least sizeof("99s").
+ */
void bootloader_format_timer(char* out, size_t out_len, uint8_t seconds);
+
+/**
+ * Format an unknown bootloader command message.
+ *
+ * out_len must be at least sizeof("Command: 255 unknown").
+ */
void bootloader_format_unknown_command(char* out, size_t out_len, uint8_t command);
#endif
diff --git a/src/memory/memory_shared.c b/src/memory/memory_shared.c
index 7545759..cc92c08 100644
--- a/src/memory/memory_shared.c
+++ b/src/memory/memory_shared.c
@@ -249,17 +249,11 @@ void memory_random_name(char* name_out)
letters[i] = 'A' + (random[i] % 26);
}
- // Format into cached name
- snprintf(
- cached_name,
- MEMORY_DEVICE_MAX_LEN_WITH_NULL,
- "BitBox %c%c%c%c",
- letters[0],
- letters[1],
- letters[2],
- letters[3]);
+ memcpy(cached_name, "BitBox ", sizeof("BitBox ") - 1);
+ memcpy(&cached_name[sizeof("BitBox ") - 1], letters, sizeof(letters));
+ cached_name[sizeof("BitBox ") - 1 + sizeof(letters)] = '\0';
}
// Copy cached result to output
- snprintf(name_out, MEMORY_DEVICE_MAX_LEN_WITH_NULL, "%s", cached_name);
+ memcpy(name_out, cached_name, MEMORY_DEVICE_MAX_LEN_WITH_NULL);
}
diff --git a/src/rust/util/src/bytes.rs b/src/rust/util/src/bytes.rs
index 1dd0d7c..e9b3cc5 100644
--- a/src/rust/util/src/bytes.rs
+++ b/src/rust/util/src/bytes.rs
@@ -2,6 +2,46 @@
use core::ffi::c_uchar;
+/// Format an unsigned integer as ASCII decimal into `out`.
+///
+/// The formatted number is left-padded to `min_width` with `pad`, null-terminated,
+/// and the byte length excluding the null terminator is returned.
+///
+/// Panics if `out` cannot fit the formatted number and null terminator.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_format_uint(
+ mut out: BytesMut,
+ value: u32,
+ min_width: u8,
+ pad: c_uchar,
+) -> usize {
+ let mut digit_count = 1;
+ let mut remaining = value;
+ while remaining >= 10 {
+ digit_count += 1;
+ remaining /= 10;
+ }
+
+ let formatted_len = core::cmp::max(digit_count, min_width as usize);
+ let out = out.as_mut();
+ assert!(formatted_len < out.len());
+
+ out[formatted_len] = 0;
+
+ let mut value = value;
+ let mut out_pos = formatted_len;
+ loop {
+ out_pos -= 1;
+ out[out_pos] = b'0' + (value % 10) as u8;
+ value /= 10;
+ if value == 0 {
+ break;
+ }
+ }
+ out[..out_pos].fill(pad);
+ formatted_len
+}
+
/// Convert bytes to hex representation
///
/// * `buf` - bytes to convert to hex.
@@ -171,4 +211,47 @@ mod tests {
);
assert_eq!(string, "0102030e0fff\0xxxxxxxxxxxxxxxxxxxxxxx");
}
+
+ #[test]
+ fn test_format_uint() {
+ let mut string = String::from("xxxxxxxxxxx");
+ let len = rust_format_uint(
+ unsafe { rust_util_bytes_mut(string.as_mut_ptr(), string.len()) },
+ 0,
+ 1,
+ b'0',
+ );
+ assert_eq!(len, 1);
+ assert_eq!(string, "0\0xxxxxxxxx");
+
+ let mut string = String::from("xxxxxxxxxxx");
+ let len = rust_format_uint(
+ unsafe { rust_util_bytes_mut(string.as_mut_ptr(), string.len()) },
+ 42,
+ 4,
+ b'0',
+ );
+ assert_eq!(len, 4);
+ assert_eq!(string, "0042\0xxxxxx");
+
+ let mut string = String::from("xxxxxxxxxxx");
+ let len = rust_format_uint(
+ unsafe { rust_util_bytes_mut(string.as_mut_ptr(), string.len()) },
+ 7,
+ 2,
+ b' ',
+ );
+ assert_eq!(len, 2);
+ assert_eq!(string, " 7\0xxxxxxxx");
+
+ let mut string = String::from("xxxxxxxxxxx");
+ let len = rust_format_uint(
+ unsafe { rust_util_bytes_mut(string.as_mut_ptr(), string.len()) },
+ u32::MAX,
+ 1,
+ b'0',
+ );
+ assert_eq!(len, 10);
+ assert_eq!(string, "4294967295\0");
+ }
}
diff --git a/src/screen.c b/src/screen.c
index 75d4659..1ed5a6b 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -32,7 +32,7 @@ slider_location_t bottom_slider = 0;
void screen_print_debug(const char* message, int duration)
{
char print[100];
- snprintf(print, sizeof(print), "%s", message);
+ util_strlcpy(print, message, sizeof(print));
screen_clear();
UG_FontSelect(&font_font_a_9X9);
UG_PutString(0, 0, print);
diff --git a/src/ui/ugui/ugui.c b/src/ui/ugui/ugui.c
index d8a9647..4c036d6 100644
--- a/src/ui/ugui/ugui.c
+++ b/src/ui/ugui/ugui.c
@@ -50,6 +50,7 @@
// SPDX-License-Identifier: Apache-2.0
#include <stdbool.h>
+#include <string.h>
#include <ui/oled/oled.h>
#include <util.h>
#include <utils_assert.h>
@@ -68,6 +69,16 @@ typedef struct {
static ug_rotation_t rotation = {0};
+static void _copy_slice(char* out, size_t out_len, const char* start, size_t len)
+{
+ if (out_len == 0) {
+ return;
+ }
+ const size_t copy_len = MIN(len, out_len - 1);
+ memcpy(out, start, copy_len);
+ out[copy_len] = '\0';
+}
+
static void _UG_PSet(UG_S16 x, UG_S16 y, UG_COLOR c)
{
ASSERT(gui != NULL);
@@ -605,7 +616,7 @@ void UG_MeasureStringCentered(UG_S16 *xout, UG_S16 *yout, const char *str)
const char* start = str;
for (c = str; *c != '\0'; c++) {
if (*c == '\n') {
- snprintf(line, sizeof(line), "%.*s", (int)(c - start), start);
+ _copy_slice(line, sizeof(line), start, (size_t)(c - start));
_UG_PutString(0, 0, &calc_width_line, &calc_height_line, line, 0, 1);
*yout += calc_height_line;
*yout += gui->char_v_space;
@@ -613,7 +624,7 @@ void UG_MeasureStringCentered(UG_S16 *xout, UG_S16 *yout, const char *str)
start = c + 1;
}
}
- snprintf(line, sizeof(line), "%.*s", (int)(c - start), start);
+ _copy_slice(line, sizeof(line), start, (size_t)(c - start));
_UG_PutString(0, 0, &calc_width_line, &calc_height_line, line, 0, 1);
*yout += calc_height_line;
*yout += gui->char_v_space;
@@ -772,12 +783,13 @@ void UG_PutStringCentered( UG_S16 x, UG_S16 y, UG_S16 width, UG_S16 height, cons
const char* start = str;
for (c = str; *c != '\0'; c++) {
if (*c == '\n' && current_line < UG_MAX_LINE_ROWS) {
- snprintf(lines[current_line], sizeof(lines[current_line]), "%.*s", (int)(c - start), start);
+ _copy_slice(
+ lines[current_line], sizeof(lines[current_line]), start, (size_t)(c - start));
current_line++;
start = c + 1;
}
}
- snprintf(lines[current_line], sizeof(lines[current_line]), "%.*s", (int)(c - start), start);
+ _copy_slice(lines[current_line], sizeof(lines[current_line]), start, (size_t)(c - start));
// calculate the height of each line
_UG_PutString(0, 0, NULL, &calc_height, "W", 0, 1);
diff --git a/src/util.c b/src/util.c
index f73d4ff..9273f01 100644
--- a/src/util.c
+++ b/src/util.c
@@ -28,6 +28,17 @@ void util_zero(volatile void* dst, size_t len)
#endif
}
+void util_strlcpy(char* dst, const char* src, size_t dst_len)
+{
+ if (dst_len == 0) {
+ return;
+ }
+ size_t len = strlen(src);
+ size_t copy_len = MIN(len, dst_len - 1);
+ memcpy(dst, src, copy_len);
+ dst[copy_len] = '\0';
+}
+
void util_uint8_to_hex(const uint8_t* in_bin, const size_t in_len, char* out)
{
rust_util_uint8_to_hex(
diff --git a/src/util.h b/src/util.h
index ccdf197..6982b11 100644
--- a/src/util.h
+++ b/src/util.h
@@ -48,6 +48,7 @@ typedef uint8_t secbool_u8;
#define secfalse_u8 0x00u
void util_zero(volatile void* dst, size_t len);
+void util_strlcpy(char* dst, const char* src, size_t dst_len);
// `out` must be of size in_len*2+1. Use BB_HEX_SIZE() to compute the size.
void util_uint8_to_hex(const uint8_t* in_bin, size_t in_len, char* out);
diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt
index d1fe71e..023f6f1 100644
--- a/test/unit-test/CMakeLists.txt
+++ b/test/unit-test/CMakeLists.txt
@@ -74,6 +74,9 @@ else()
)
if(TEST_NAME STREQUAL "bootloader_format")
target_sources(${EXE} PRIVATE ${CMAKE_SOURCE_DIR}/src/bootloader/bootloader_format.c)
+ target_include_directories(${EXE} PRIVATE
+ ${CMAKE_SOURCE_DIR}/external/asf4-drivers/hal/utils/include
+ )
endif()
add_test(NAME test_${TEST_NAME} COMMAND ${EXE})
endforeach()
diff --git a/test/unit-test/test_bootloader_format.c b/test/unit-test/test_bootloader_format.c
index bba6d9c..0370c55 100644
--- a/test/unit-test/test_bootloader_format.c
+++ b/test/unit-test/test_bootloader_format.c
@@ -59,6 +59,9 @@ static void test_timer(void** state)
bootloader_format_timer(out, sizeof(out), 9);
assert_string_equal(out, "9s");
+
+ bootloader_format_timer(out, sizeof(out), 10);
+ assert_string_equal(out, "10s");
}
static void test_unknown_command(void** state)
diff --git a/test/unit-test/test_ugui.c b/test/unit-test/test_ugui.c
index aadcaca..4c3710a 100644
--- a/test/unit-test/test_ugui.c
+++ b/test/unit-test/test_ugui.c
@@ -80,11 +80,28 @@ static void _test_ugui_render_rotated_180(void** state)
assert_int_equal(last_y, 24);
}
+static void _test_ugui_measure_string_centered(void** state)
+{
+ (void)state;
+ UG_Init(&gui, _set_pixel, &font_font_a_11X10, 128, 64);
+
+ UG_S16 centered_width = 0;
+ UG_S16 centered_height = 0;
+ UG_S16 single_line_width = 0;
+ UG_S16 single_line_height = 0;
+ UG_MeasureStringCentered(¢ered_width, ¢ered_height, "A\nAA");
+ UG_MeasureStringNoBreak(&single_line_width, &single_line_height, "AA");
+
+ assert_int_equal(centered_width, single_line_width);
+ assert_int_equal(centered_height, 2 * single_line_height + 2 * gui.char_v_space);
+}
+
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(_test_ugui_word_wrap),
cmocka_unit_test(_test_ugui_render_rotated_180),
+ cmocka_unit_test(_test_ugui_measure_string_centered),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
diff --git a/test/unit-test/test_util.c b/test/unit-test/test_util.c
index b2a9e9d..9622823 100644
--- a/test/unit-test/test_util.c
+++ b/test/unit-test/test_util.c
@@ -36,10 +36,28 @@ static void test_minmax(void** state)
assert_int_not_equal(res, 5);
}
+static void test_util_strlcpy(void** state)
+{
+ (void)state;
+
+ char out[] = "xxxx";
+ util_strlcpy(out, "abc", sizeof(out));
+ assert_string_equal(out, "abc");
+
+ char truncated[5];
+ util_strlcpy(truncated, "truncated", sizeof(truncated));
+ assert_string_equal(truncated, "trun");
+
+ char zero_len = 'x';
+ util_strlcpy(&zero_len, "abc", 0);
+ assert_int_equal(zero_len, 'x');
+}
+
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_minmax),
+ cmocka_unit_test(test_util_strlcpy),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.