Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(cfusa
VERSION 0.5.47
VERSION 0.5.48
DESCRIPTION "C functional safety toolkit"
LANGUAGES C
)
Expand Down Expand Up @@ -160,6 +160,7 @@ cfusa_test(test_qualify_vv)
cfusa_test(test_mcdc)
cfusa_test(test_gap_coverage)
cfusa_test(test_xfusa_v114)
cfusa_test(test_xfusa_audit_20260728)

# ---- Installation ----
include(GNUInstallDirs)
Expand Down
14 changes: 7 additions & 7 deletions cmd/cfusa/cmd_analyze.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,19 @@

static const cfusa_rule_t analyze_rules[] = {
{"CFUSA-A001","analyze","Unsafe string functions",
"Unbounded string operations risk overflow","CERT-C STR31-C",rule_a001},
"Unbounded string operations risk overflow","cert-c","STR31-C",rule_a001},
{"CFUSA-A002","analyze","Unchecked allocation",
"malloc/calloc/realloc return must be checked","CERT-C MEM32-C",rule_a002},
"malloc/calloc/realloc return must be checked","cert-c","MEM32-C",rule_a002},
{"CFUSA-A003","analyze","Signed/unsigned comparison",
"Comparison of signed and sizeof (unsigned)","CERT-C INT02-C",rule_a003},
"Comparison of signed and sizeof (unsigned)","cert-c","INT02-C",rule_a003},
{"CFUSA-A004","analyze","Integer boundary",
"INT_MAX/MIN/UINT_MAX usage without guard","CERT-C INT30-C",rule_a004},
"INT_MAX/MIN/UINT_MAX usage without guard","cert-c","INT30-C",rule_a004},

Check notice

Code scanning / c-FuSa

integer boundary constant referenced — verify overflow guard is in place (CERT-C INT30-C, INT32-C) Note

integer boundary constant referenced — verify overflow guard is in place (CERT-C INT30-C, INT32-C)
{"CFUSA-A005","analyze","Assert in production",
"assert() may be compiled out in release builds","CERT-C MSC11-C",rule_a005},
"assert() may be compiled out in release builds","cert-c","MSC11-C",rule_a005},
{"CFUSA-A006","analyze","Pointer arithmetic",
"Pointer arithmetic requires bounds verification","MISRA-C:2012 R18.4",rule_a006},
"Pointer arithmetic requires bounds verification","misra-c","R18.4",rule_a006},
{"CFUSA-A007","analyze","Unchecked system call",
"System call return values must be checked","CERT-C ERR33-C",rule_a007},
"System call return values must be checked","cert-c","ERR33-C",rule_a007},
};
#define N_ANALYZE_RULES ((int)(sizeof(analyze_rules)/sizeof(analyze_rules[0])))

Expand Down
40 changes: 20 additions & 20 deletions cmd/cfusa/cmd_cyber.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,26 +538,26 @@ static int rule_cy020(const char *dir,const cfusa_config_t *cfg,cfusa_report_t *
/* ---- rule table ---- */

static const cfusa_rule_t cyber_rules[] = {
{"CFUSA-CY001","cyber","Buffer copy size check","CWE-120","ISO 21434 / CERT-C STR31-C",rule_cy001},
{"CFUSA-CY002","cyber","Format string injection","CWE-134","CERT-C FIO30-C",rule_cy002},
{"CFUSA-CY003","cyber","OS command injection","CWE-78","ISO 21434 CAL3",rule_cy003},
{"CFUSA-CY004","cyber","NULL pointer dereference","CWE-476","CERT-C EXP34-C",rule_cy004},
{"CFUSA-CY005","cyber","Integer overflow in alloc","CWE-190","CERT-C INT30-C",rule_cy005},
{"CFUSA-CY006","cyber","Use after free","CWE-416","CERT-C MEM30-C",rule_cy006},
{"CFUSA-CY007","cyber","Double free","CWE-415","CERT-C MEM31-C",rule_cy007},
{"CFUSA-CY008","cyber","Insecure temp file","CWE-377","CERT-C FIO21-C",rule_cy008},
{"CFUSA-CY009","cyber","Broken crypto","CWE-327","ISO 21434 CS-7",rule_cy009},
{"CFUSA-CY010","cyber","Dangerous function","CWE-676","CERT-C",rule_cy010},
{"CFUSA-CY011","cyber","SSRF via curl URL variable","CWE-918","ISO 21434 CS-10",rule_cy011},
{"CFUSA-CY012","cyber","Debug socket option exposed","CWE-489","CERT-C",rule_cy012},
{"CFUSA-CY013","cyber","Archive path traversal (zip-slip)","CWE-23","CERT-C FIO02-C",rule_cy013},
{"CFUSA-CY014","cyber","Weak/deprecated TLS method","CWE-326","CERT-C MSC61-C",rule_cy014},
{"CFUSA-CY015","cyber","SQL injection via sprintf","CWE-89","CERT-C FIO30-C",rule_cy015},
{"CFUSA-CY016","cyber","Permissive directory mode","CWE-732","CERT-C FIO06-C",rule_cy016},
{"CFUSA-CY017","cyber","Permissive file mode","CWE-732","CERT-C FIO06-C",rule_cy017},
{"CFUSA-CY018","cyber","Path traversal from argv/env","CWE-22","CERT-C FIO02-C",rule_cy018},
{"CFUSA-CY019","cyber","TOCTOU race (access before open)","CWE-362","CERT-C FIO45-C",rule_cy019},
{"CFUSA-CY020","cyber","Predictable /tmp path","CWE-377","CERT-C FIO21-C",rule_cy020},
{"CFUSA-CY001","cyber","Buffer copy size check","CWE-120","cert-c","STR31-C",rule_cy001},
{"CFUSA-CY002","cyber","Format string injection","CWE-134","cert-c","FIO30-C",rule_cy002},
{"CFUSA-CY003","cyber","OS command injection","CWE-78","iso21434","CAL3",rule_cy003},
{"CFUSA-CY004","cyber","NULL pointer dereference","CWE-476","cert-c","EXP34-C",rule_cy004},
{"CFUSA-CY005","cyber","Integer overflow in alloc","CWE-190","cert-c","INT30-C",rule_cy005},
{"CFUSA-CY006","cyber","Use after free","CWE-416","cert-c","MEM30-C",rule_cy006},
{"CFUSA-CY007","cyber","Double free","CWE-415","cert-c","MEM31-C",rule_cy007},
{"CFUSA-CY008","cyber","Insecure temp file","CWE-377","cert-c","FIO21-C",rule_cy008},
{"CFUSA-CY009","cyber","Broken crypto","CWE-327","iso21434","CS-7",rule_cy009},
{"CFUSA-CY010","cyber","Dangerous function","CWE-676","cert-c",NULL,rule_cy010},
{"CFUSA-CY011","cyber","SSRF via curl URL variable","CWE-918","iso21434","CS-10",rule_cy011},
{"CFUSA-CY012","cyber","Debug socket option exposed","CWE-489","cert-c",NULL,rule_cy012},
{"CFUSA-CY013","cyber","Archive path traversal (zip-slip)","CWE-23","cert-c","FIO02-C",rule_cy013},
{"CFUSA-CY014","cyber","Weak/deprecated TLS method","CWE-326","cert-c","MSC61-C",rule_cy014},
{"CFUSA-CY015","cyber","SQL injection via sprintf","CWE-89","cert-c","FIO30-C",rule_cy015},
{"CFUSA-CY016","cyber","Permissive directory mode","CWE-732","cert-c","FIO06-C",rule_cy016},
{"CFUSA-CY017","cyber","Permissive file mode","CWE-732","cert-c","FIO06-C",rule_cy017},
{"CFUSA-CY018","cyber","Path traversal from argv/env","CWE-22","cert-c","FIO02-C",rule_cy018},
{"CFUSA-CY019","cyber","TOCTOU race (access before open)","CWE-362","cert-c","FIO45-C",rule_cy019},
{"CFUSA-CY020","cyber","Predictable /tmp path","CWE-377","cert-c","FIO21-C",rule_cy020},
};
#define N_CYBER_RULES ((int)(sizeof(cyber_rules)/sizeof(cyber_rules[0])))

Expand Down
8 changes: 7 additions & 1 deletion cmd/cfusa/cmd_do178.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,14 @@ int cmd_do178(int argc, char **argv)
" \"applicable\": %d, \"total\": %d,\n \"objectives\": [\n",
ts, dir, cfg.project,(char)('A'+dal_col),applicable,total);
int first=1;
int satisfied=0;
for (int i=0;OBJECTIVES[i].id;i++){
int applies[4]={OBJECTIVES[i].dal_a,OBJECTIVES[i].dal_b,
OBJECTIVES[i].dal_c,OBJECTIVES[i].dal_d};
if(!applies[dal_col]) continue;
int ok = OBJECTIVES[i].evidence_file &&
do178_file_exists(dir, OBJECTIVES[i].evidence_file);
if (ok) satisfied++;
const char *status = ok ? "satisfied" : "gap";
fprintf(out,"%s {\"id\":\"%s\",\"process\":\"%s\","
"\"title\":\"%s\",\"findings\":[],\"status\":\"%s\"}",
Expand All @@ -225,7 +227,11 @@ int cmd_do178(int argc, char **argv)
OBJECTIVES[i].objective, status);
first=0;
}
fprintf(out,"\n ]\n}\n");
fprintf(out,
"\n ],\n"
" \"summary\": {\"total\": %d, \"satisfied\": %d, \"partial\": 0, \"gaps\": %d}\n"
"}\n",
applicable, satisfied, applicable - satisfied);
} else {
fprintf(out,"DO-178C Annex A Gap Report\n"
"Project: %s v%s DAL: %c Generated: %s\n"
Expand Down
13 changes: 10 additions & 3 deletions cmd/cfusa/cmd_fmea.c
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ int cmd_fmea(int argc, char **argv)
}

int qb_gate = run_quality_bar(dir, fresh_hash, &attestation, require_attestation);
int attestation_valid = cfusa_qb_attestation_valid(&attestation, fresh_hash);

int cov_gate = 0;
if (min_coverage > 0 && coverage_pct < min_coverage) {
Expand All @@ -382,15 +381,23 @@ int cmd_fmea(int argc, char **argv)
cov_gate = 1;
}

/* x-FuSa spec §1.6.2 MUST (carry-forward across regeneration): a prior
* attestation is carried forward onto the regenerated document verbatim
* whenever one was read back, not only when it is still hash-valid —
* staleness is then automatic (a consumer recomputes contentHash and
* falls back to "heuristic" on mismatch), so gating *emission* on
* attestation_valid would silently erase a real prior review the moment
* content changes. */
#define WRITE_ATTESTATION(fp) do { \
if (attestation_valid) { \
if (attestation.present) { \
fprintf((fp), ",\n \"attestation\": {\n" \
" \"status\": \"reviewed\",\n" \
" \"status\": \"%s\",\n" \
" \"implementationAuthor\": \"%s\",\n" \
" \"independentReviewer\": \"%s\",\n" \
" \"reviewedAt\": \"%s\",\n" \
" \"contentHash\": \"%s\"\n" \
" }\n", \
attestation.status[0] ? attestation.status : "heuristic", \
attestation.implementation_author, attestation.independent_reviewer, \
attestation.reviewed_at, attestation.content_hash); \
} else { \
Expand Down
13 changes: 7 additions & 6 deletions cmd/cfusa/cmd_hara.c
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,15 @@ static int do_init(const char *dir, const char *project)
cfusa_str_escape_json(project, esc_project, sizeof(esc_project));

/* x-FuSa spec §1.6 rule 1: an unanalyzed section MUST be an empty
* array, never a dummy/placeholder row. */
* array, never a dummy/placeholder row.
*
* §1.2.5: .fusa-hara.json is an INPUT file (like .fusa-reqs.json), not
* a report document — it MUST NOT carry the §3.1 report envelope
* (schemaVersion/kind/tool/toolVersion/language). Its schema is just
* {project, standard, createdAt, operationalSituations, hazards,
* safetyGoals, attestation?}. */
fprintf(f,
"{\n"
" \"schemaVersion\": \"" CFUSA_SCHEMA_VERSION "\",\n"
" \"kind\": \"hara\",\n"
" \"tool\": \"c-FuSa\",\n"
" \"toolVersion\": \"" CFUSA_VERSION_STRING "\",\n"
" \"language\": \"c\",\n"
" \"project\": \"%s\",\n"
" \"standard\": \"iso26262\",\n"
" \"createdAt\": \"%s\",\n"
Expand Down
6 changes: 5 additions & 1 deletion cmd/cfusa/cmd_iec61508.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,11 @@ int cmd_iec61508(int argc, char **argv)
level_str, status);
first = 0;
}
fprintf(out, "\n ]\n}\n");
fprintf(out,
"\n ],\n"
" \"summary\": {\"total\": %d, \"satisfied\": %d, \"partial\": %d, \"gaps\": %d}\n"
"}\n",
covered + gaps_r + gaps_m, covered, gaps_r, gaps_m);
} else if (!strcmp(fmt_s, "text")) {
fprintf(out, "IEC 61508 Parts 1-3 Gap Report — %s (target %s)\n", cfg.project, sil);
fprintf(out, "=====================================================\n\n");
Expand Down
8 changes: 6 additions & 2 deletions cmd/cfusa/cmd_iec62443.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ int cmd_iec62443(int argc, char **argv)
" \"language\": \"c\",\n"
" \"generatedAt\": \"%s\",\n"
" \"projectRoot\": \"%s\",\n"
" \"standard\": \"iec62443\",\n"
" \"standard\": \"iec62443-4-2\",\n"
" \"project\": \"%s\",\n"
" \"sl\": \"%s\",\n"
" \"hasProjectConfig\": %s,\n"
Expand Down Expand Up @@ -192,7 +192,11 @@ int cmd_iec62443(int argc, char **argv)
level_str, status);
first = 0;
}
fprintf(out, "\n ]\n}\n");
fprintf(out,
"\n ],\n"
" \"summary\": {\"total\": %d, \"satisfied\": %d, \"partial\": %d, \"gaps\": %d}\n"
"}\n",
covered + gaps_r + gaps_m, covered, gaps_r, gaps_m);
} else if (!strcmp(fmt_s, "text")) {
fprintf(out, "IEC 62443 Gap Report — %s (target %s, profile 4-2)\n", cfg.project, sl);
fprintf(out, "==================================================\n\n");
Expand Down
13 changes: 12 additions & 1 deletion cmd/cfusa/cmd_iso21434.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,18 @@ int cmd_iso21434(int argc, char **argv)
o->id, o->description, status);
first = 0;
}
fprintf(out, "\n ]\n}\n");
/* §9.3 canonical status enum is satisfied|partial|gap — "manual" and
* "na" (used above for objectives requiring non-tool-checkable
* organisational evidence, or not applicable at this CAL) are not
* members of that closed enum, so a spec-conformant consumer's
* fail-safe mapping treats them as "gap" (see §9.3 status note).
* The summary below counts them the same way for consistency with
* that fail-safe rule. */
fprintf(out,
"\n ],\n"
" \"summary\": {\"total\": %d, \"satisfied\": %d, \"partial\": 0, \"gaps\": %d}\n"
"}\n",
pass + gap + manual, pass, gap + manual);
} else if (!strcmp(fmt, "text")) {
fprintf(out, "ISO 21434 Gap Report — %s (%s)\n", cfg.project, cal);
fprintf(out, "============================================\n\n");
Expand Down
22 changes: 15 additions & 7 deletions cmd/cfusa/cmd_iso26262.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ int cmd_iso26262(int argc, char **argv)
return 2;
}

int covered = 0, gaps = 0, na = 0;
int covered = 0, gaps = 0, gaps_r = 0, na = 0;
for (int i = 0; OBJECTIVES[i].clause; i++) {
const iso26262_row_t *r = &OBJECTIVES[i];
int req = (level==1) ? r->asil_a :
Expand All @@ -133,7 +133,9 @@ int cmd_iso26262(int argc, char **argv)
if (req == 0) { na++; continue; }
int ok = r->cfusa_rule != NULL;
if (!ok && r->evidence_file) ok = file_exists_in_dir(dir, r->evidence_file);
if (ok) covered++; else gaps++;
if (ok) covered++;
else if (req == 2) gaps_r++;
else gaps++;
}

FILE *out = stdout;
Expand All @@ -155,9 +157,10 @@ int cmd_iso26262(int argc, char **argv)
" \"asil\": \"%s\",\n"
" \"covered\": %d,\n"
" \"gaps\": %d,\n"
" \"recommendedGaps\": %d,\n"
" \"na\": %d,\n"
" \"objectives\": [\n",
ts, dir, cfg.project, asil, covered, gaps, na);
ts, dir, cfg.project, asil, covered, gaps, gaps_r, na);
int first = 1;
for (int i = 0; OBJECTIVES[i].clause; i++) {
const iso26262_row_t *r = &OBJECTIVES[i];
Expand All @@ -178,7 +181,11 @@ int cmd_iso26262(int argc, char **argv)
status);
first = 0;
}
fprintf(out, "\n ]\n}\n");
fprintf(out,
"\n ],\n"
" \"summary\": {\"total\": %d, \"satisfied\": %d, \"partial\": %d, \"gaps\": %d}\n"
"}\n",
covered + gaps_r + gaps, covered, gaps_r, gaps);
} else if (!strcmp(fmt_s, "text")) {
fprintf(out, "ISO 26262 Parts 6-11 Gap Report — %s (target %s)\n",
cfg.project, asil);
Expand All @@ -203,9 +210,10 @@ int cmd_iso26262(int argc, char **argv)
r->clause, r->title, rule_s, status);
}

fprintf(out, "\nSummary: %d covered, %d gap(s), %d not applicable for %s\n",
covered, gaps, na, asil);
if (gaps > 0)
fprintf(out, "\nSummary: %d covered, %d gap(s), %d recommended gap(s), "
"%d not applicable for %s\n",
covered, gaps, gaps_r, na, asil);
if (gaps > 0 || gaps_r > 0)
fprintf(out, "Review gaps and add manual evidence or custom cfusa rules.\n");
} else {
if (output && out != stdout) fclose(out);
Expand Down
52 changes: 39 additions & 13 deletions cmd/cfusa/cmd_lint.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include "cfusa/engine.h"
#include "cfusa/report.h"
Expand Down Expand Up @@ -186,6 +187,33 @@
int in_block_comment; /* persists across fgets() iterations */
} l004_ctx_t;

/* Word-boundary-aware self-call detector for CFUSA-L004. Unlike the
* generic cfusa_match_outside_string() substring match (which is a
* deliberate, documented non-word-boundary checker used elsewhere for
* fixed dangerous-function-name lookups), a self-call check additionally
* requires an identifier boundary immediately before the candidate match
* — otherwise a callee whose name merely *ends with* the caller's name
* (e.g. static int evaluate(...) calling helper_evaluate(...), or
* rcp_e2e_wd_evaluate(...)) is misreported as recursion. */
static int l004_self_call(const char *line, const char *fn_name)
{
size_t flen = strlen(fn_name);
if (flen == 0) return 0;
int in_str = 0;
const char *p = line;
while (*p) {
if (*p == '"' && (p == line || p[-1] != '\\'))
in_str = !in_str;
if (!in_str && strncmp(p, fn_name, flen) == 0 && p[flen] == '(') {
int boundary_ok = (p == line) ||
!(isalnum((unsigned char)p[-1]) || p[-1] == '_');
if (boundary_ok) return 1;
}
p++;
}
return 0;
}

static int l004_file(const char *path, void *vctx)
{
l004_ctx_t *ctx = vctx;
Expand Down Expand Up @@ -288,9 +316,7 @@
/* Self-call check. Skip the line where the function was first
* detected: the signature always contains "fn_name(" naturally. */
if (!fn_just_detected && ctx->in_fn && ctx->fn_name[0] && brace > 0) {
char call[130];
snprintf(call, sizeof(call), "%s(", ctx->fn_name);
if (cfusa_match_outside_string(line, call)) {
if (l004_self_call(line, ctx->fn_name)) {
cfusa_report_add(ctx->rpt,
"CFUSA-L004", CFUSA_CATEGORY_LINT, SEV_ERROR,
path, lineno,
Expand Down Expand Up @@ -502,25 +528,25 @@

static const cfusa_rule_t lint_rules[] = {
{"CFUSA-L001","lint","Function length",
"Functions should not exceed max_function_lines","MISRA-C:2012 R15.5",rule_l001},
"Functions should not exceed max_function_lines","misra-c","R15.5",rule_l001},
{"CFUSA-L002","lint","No goto",
"Goto statements are prohibited","MISRA-C:2012 R15.1",rule_l002},
"Goto statements are prohibited","misra-c","R15.1",rule_l002},
{"CFUSA-L003","lint","No dynamic memory",
"malloc/calloc/realloc/free prohibited","MISRA-C:2012 R21.3",rule_l003},
"malloc/calloc/realloc/free prohibited","misra-c","R21.3",rule_l003},
{"CFUSA-L004","lint","No recursion",
"Recursive functions are prohibited","MISRA-C:2012 R17.2",rule_l004},
"Recursive functions are prohibited","misra-c","R17.2",rule_l004},
{"CFUSA-L005","lint","No #undef",
"#undef shall not be used","MISRA-C:2012 R20.5",rule_l005},
"#undef shall not be used","misra-c","R20.5",rule_l005},
{"CFUSA-L006","lint","No setjmp/longjmp",
"Non-local jumps shall not be used","MISRA-C:2012 R17.4",rule_l006},
"Non-local jumps shall not be used","misra-c","R17.4",rule_l006},
{"CFUSA-L007","lint","Mutable static variable",
"Mutable statics reduce testability","MISRA-C:2012 R8.9",rule_l007},
"Mutable statics reduce testability","misra-c","R8.9",rule_l007},
{"CFUSA-L008","lint","Avoid void*",
"Conversions from void* should be avoided","MISRA-C:2012 R11.5",rule_l008},
"Conversions from void* should be avoided","misra-c","R11.5",rule_l008},

Check warning

Code scanning / c-FuSa

use of void* pointer — MISRA-C 2012 Rule 11.5: conversion from void* should be avoided Warning

use of void* pointer — MISRA-C 2012 Rule 11.5: conversion from void* should be avoided
{"CFUSA-L009","lint","No #pragma",
"#pragma reduces portability","MISRA-C:2012 R20.10",rule_l009},
"#pragma reduces portability","misra-c","R20.10",rule_l009},
{"CFUSA-L010","lint","errno usage",
"errno must be zeroed before use","MISRA-C:2012 R22.8",rule_l010},
"errno must be zeroed before use","misra-c","R22.8",rule_l010},

Check notice

Code scanning / c-FuSa

use of errno — MISRA-C 2012 Rule 22.8: errno value must be set to zero before calling a function; verify correct usage pattern Note

use of errno — MISRA-C 2012 Rule 22.8: errno value must be set to zero before calling a function; verify correct usage pattern
};
#define N_LINT_RULES ((int)(sizeof(lint_rules)/sizeof(lint_rules[0])))

Expand Down
Loading
Loading