contiki-ng+tryton: 5-MOAD scan — contiki-0001 CWE-312 LwM2M PSK logged; tryton MOADs 0002-0005 CLEAN
This commit is contained in:
parent
0f13a71cb4
commit
3e88e64a6a
6 changed files with 267 additions and 2 deletions
|
|
@ -15,8 +15,8 @@ Rule: clone, scan, delete clone after. Keep disk under 90%.
|
|||
|
||||
## Priority 2 — ERP/Business not yet scanned
|
||||
|
||||
- [ ] Dolibarr (PHP, ERP)
|
||||
- [ ] Tryton (Python, ERP)
|
||||
- [x] Dolibarr (PHP, ERP) — scanned (see prior wave)
|
||||
- [x] Tryton (Python, ERP) — tryton-0001 (_save_values previous list O(T*P)); MOADs 0002/0003/0004/0005 CLEAN
|
||||
- [ ] xTuple PostBooks (C++/JS, ERP)
|
||||
- [ ] SuiteCRM (PHP, CRM)
|
||||
- [ ] InvoiceNinja (PHP, invoicing)
|
||||
|
|
|
|||
26
defects/contiki-0001/patch/contiki-0001.patch
Normal file
26
defects/contiki-0001/patch/contiki-0001.patch
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
--- a/os/services/lwm2m/lwm2m-security.c
|
||||
+++ b/os/services/lwm2m/lwm2m-security.c
|
||||
@@ -204,10 +204,8 @@ write_security_object(lwm2m_object_instance_t *object,
|
||||
lwm2m_object_read_string(ctx, ctx->inbuf->buffer, ctx->inbuf->size, security->public_key, LWM2M_SECURITY_KEY_SIZE);
|
||||
security->public_key_len = ctx->last_value_len;
|
||||
|
||||
- LOG_DBG("Writing client PKI: len: %"PRIu16" '", ctx->last_value_len);
|
||||
- LOG_DBG_COAP_STRING((const char *)security->public_key,
|
||||
- ctx->last_value_len);
|
||||
- LOG_DBG_("'\n");
|
||||
+ /* CWE-312: do not log PKI credential material, even at DBG level */
|
||||
+ LOG_DBG("Writing client PKI: len: %"PRIu16"\n", ctx->last_value_len);
|
||||
break;
|
||||
case LWM2M_SECURITY_KEY_ID:
|
||||
lwm2m_object_read_string(ctx, ctx->inbuf->buffer, ctx->inbuf->size, security->secret_key, LWM2M_SECURITY_KEY_SIZE);
|
||||
@@ -215,10 +213,8 @@ write_security_object(lwm2m_object_instance_t *object,
|
||||
|
||||
- LOG_DBG("Writing secret key: len: %"PRIu16" '", ctx->last_value_len);
|
||||
- LOG_DBG_COAP_STRING((const char *)security->secret_key,
|
||||
- ctx->last_value_len);
|
||||
- LOG_DBG_("'\n");
|
||||
+ /* CWE-312: do not log PSK secret key material, even at DBG level */
|
||||
+ LOG_DBG("Writing secret key: len: %"PRIu16"\n", ctx->last_value_len);
|
||||
|
||||
break;
|
||||
}
|
||||
BIN
defects/contiki-0001/test/ContikiLwm2mSecretKeyLogTest.class
Normal file
BIN
defects/contiki-0001/test/ContikiLwm2mSecretKeyLogTest.class
Normal file
Binary file not shown.
138
defects/contiki-0001/test/ContikiLwm2mSecretKeyLogTest.java
Normal file
138
defects/contiki-0001/test/ContikiLwm2mSecretKeyLogTest.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
/**
|
||||
* Unit test for Contiki-NG CWE-312 defect: lwm2m-security.c logs LwM2M
|
||||
* PSK secret key and PKI public key verbatim at LOG_DBG level.
|
||||
*
|
||||
* CWE-312: Cleartext Storage of Sensitive Information
|
||||
* MOAD-0004: The Logged Secret
|
||||
*
|
||||
* Defect location:
|
||||
* os/services/lwm2m/lwm2m-security.c write_security_object()
|
||||
* LWM2M_SECURITY_CLIENT_PKI_ID case: logs public_key bytes via LOG_DBG_COAP_STRING
|
||||
* LWM2M_SECURITY_KEY_ID case: logs secret_key bytes via LOG_DBG_COAP_STRING
|
||||
*
|
||||
* Risk: LOG_CONF_LEVEL_LWM2M defaults to LOG_LEVEL_NONE in production builds
|
||||
* but is runtime-configurable and example project-conf.h files set it to
|
||||
* LOG_LEVEL_DBG. When debug logging is enabled, the raw PSK secret key and
|
||||
* TLS client PKI material are written to serial/UART output in cleartext.
|
||||
* On constrained IoT devices these logs are often captured by gateway nodes,
|
||||
* stored in cloud backends, and may appear in support tickets or monitoring
|
||||
* dashboards, exposing the device's network authentication credentials.
|
||||
*
|
||||
* Fix: remove LOG_DBG_COAP_STRING calls for credential fields; keep only
|
||||
* the length metadata which is safe to log.
|
||||
*/
|
||||
public class ContikiLwm2mSecretKeyLogTest {
|
||||
|
||||
/** Simulate the DEFECTIVE log output: includes raw key bytes */
|
||||
static String defectiveLogOutput(byte[] secretKey, int keyLen) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Writing secret key: len: ").append(keyLen).append(" '");
|
||||
// LOG_DBG_COAP_STRING dumps raw bytes
|
||||
for (int i = 0; i < keyLen; i++) {
|
||||
sb.append((char) secretKey[i]);
|
||||
}
|
||||
sb.append("'\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Simulate the FIXED log output: only length, no key bytes */
|
||||
static String fixedLogOutput(byte[] secretKey, int keyLen) {
|
||||
return "Writing secret key: len: " + keyLen + "\n";
|
||||
}
|
||||
|
||||
/** Check whether log output contains credential material */
|
||||
static boolean containsCredentialMaterial(String logLine, byte[] key) {
|
||||
// Check if any 4+ byte subsequence of the key appears in the log
|
||||
if (key.length < 4) return false;
|
||||
String keyStr = new String(key);
|
||||
// Look for 4-char windows of the key in the log
|
||||
for (int i = 0; i <= key.length - 4; i++) {
|
||||
String window = new String(Arrays.copyOfRange(key, i, i + 4));
|
||||
if (logLine.contains(window)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Contiki-NG CWE-312: lwm2m-security.c PSK/PKI key logged at LOG_DBG");
|
||||
System.out.println("============================================================");
|
||||
|
||||
boolean allPass = true;
|
||||
|
||||
// Test 1: defective output contains secret key bytes
|
||||
byte[] psk = "my-iot-preshared-key-1234".getBytes();
|
||||
String defLog = defectiveLogOutput(psk, psk.length);
|
||||
boolean defectLeaks = containsCredentialMaterial(defLog, psk);
|
||||
System.out.printf("DEFECT leaks PSK in log: %s (expected: true)%n", defectLeaks);
|
||||
if (!defectLeaks) {
|
||||
System.out.println("FAIL: defective log should contain credential material");
|
||||
allPass = false;
|
||||
}
|
||||
|
||||
// Test 2: fixed output does NOT contain secret key bytes
|
||||
String fixLog = fixedLogOutput(psk, psk.length);
|
||||
boolean fixLeaks = containsCredentialMaterial(fixLog, psk);
|
||||
System.out.printf("FIXED leaks PSK in log: %s (expected: false)%n", fixLeaks);
|
||||
if (fixLeaks) {
|
||||
System.out.println("FAIL: fixed log must not contain credential material");
|
||||
allPass = false;
|
||||
}
|
||||
|
||||
// Test 3: fixed output still contains diagnostic length info
|
||||
boolean fixHasLen = fixLog.contains("len: " + psk.length);
|
||||
System.out.printf("FIXED retains length info: %s (expected: true)%n", fixHasLen);
|
||||
if (!fixHasLen) {
|
||||
System.out.println("FAIL: fixed log should retain length for diagnostics");
|
||||
allPass = false;
|
||||
}
|
||||
|
||||
// Test 4: PKI public_key scenario (same pattern, same fix)
|
||||
// Use a key value that does not overlap with the fixed log message text
|
||||
byte[] pki = "XZ99-pki-auth-secret-0xDEAD".getBytes();
|
||||
String defPkiLog = "Writing client PKI: len: " + pki.length + " '" + new String(pki) + "'\n";
|
||||
String fixPkiLog = "Writing client PKI: len: " + pki.length + "\n";
|
||||
boolean defPkiLeaks = containsCredentialMaterial(defPkiLog, pki);
|
||||
boolean fixPkiLeaks = containsCredentialMaterial(fixPkiLog, pki);
|
||||
System.out.printf("DEFECT PKI leaks: %s FIXED PKI leaks: %s (expected: true, false)%n",
|
||||
defPkiLeaks, fixPkiLeaks);
|
||||
if (!defPkiLeaks || fixPkiLeaks) {
|
||||
System.out.println("FAIL: PKI credential logging assertions failed");
|
||||
allPass = false;
|
||||
}
|
||||
|
||||
// Test 5: binary key material (PSK is often binary, not just ASCII)
|
||||
byte[] binPsk = {0x4b, 0x3a, (byte)0xff, 0x12, 0x7e, 0x01, 0x5c, (byte)0x88};
|
||||
String defBinLog = defectiveLogOutput(binPsk, binPsk.length);
|
||||
String fixBinLog = fixedLogOutput(binPsk, binPsk.length);
|
||||
// For binary keys, check the log length difference
|
||||
boolean defBinLonger = defBinLog.length() > fixBinLog.length();
|
||||
System.out.printf("DEFECT binary PSK log longer than fixed: %s (expected: true)%n", defBinLonger);
|
||||
if (!defBinLonger) {
|
||||
System.out.println("FAIL: defective binary-PSK log should be longer");
|
||||
allPass = false;
|
||||
}
|
||||
|
||||
System.out.println("------------------------------------------------------------");
|
||||
System.out.println("Severity: MEDIUM — CWE-312 credential exposure in debug log");
|
||||
System.out.println(" Trigger: LOG_CONF_LEVEL_LWM2M >= LOG_LEVEL_DBG (set in");
|
||||
System.out.println(" examples/libs/logging/project-conf.h and common debugging");
|
||||
System.out.println(" workflows; runtime-configurable via Contiki log module)");
|
||||
System.out.println(" Impact: PSK secret and PKI material written to UART/serial");
|
||||
System.out.println(" in cleartext; gateway capture -> cloud storage -> exposure");
|
||||
System.out.println("Fix: Remove LOG_DBG_COAP_STRING for secret_key and public_key;");
|
||||
System.out.println(" retain length-only log for diagnostic value without exposure.");
|
||||
System.out.println("------------------------------------------------------------");
|
||||
|
||||
if (allPass) {
|
||||
System.out.println("ALL PASS");
|
||||
} else {
|
||||
System.out.println("SOME TESTS FAILED");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
defects/contiki-ng/SCAN.md
Normal file
51
defects/contiki-ng/SCAN.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
## Contiki-NG 5-MOAD Scan — 2026-03-31
|
||||
|
||||
Target: https://github.com/contiki-ng/contiki-ng (depth=1)
|
||||
Focus: os/net/, os/lib/, os/sys/, os/services/lwm2m/
|
||||
|
||||
### MOAD-0001 (CWE-407) — CLEAN
|
||||
|
||||
Neighbor table lookup (`index_from_lladdr` in `os/net/nbr-table.c`) is O(N) per
|
||||
per-packet call, where N = `NBR_TABLE_CONF_MAX_NEIGHBORS` (default 16). This is
|
||||
O(N) per event, not O(N^2). N is bounded by a compile-time constant, not user input.
|
||||
|
||||
Route lookup (`uip_ds6_route_lookup` in `os/net/ipv6/uip-ds6-route.c`) is similarly
|
||||
O(R) per packet, R bounded by `NETSTACK_MAX_ROUTE_ENTRIES` (default 16).
|
||||
|
||||
TSCH link lookup (`tsch_schedule_get_link_by_handle`) is O(S*L) for S slotframes
|
||||
and L links. Both are bounded small in practice for IoT deployments.
|
||||
|
||||
No unbounded O(N^2) hot-path defect found. The linked-list structures are
|
||||
appropriate for constrained IoT with small, fixed-size neighbor tables.
|
||||
|
||||
### MOAD-0002 (Intertangle) — CLEAN
|
||||
|
||||
Contiki-NG uses a protothread/event-driven cooperative scheduler. Each subsystem
|
||||
(TSCH, RPL, CoAP, LwM2M, 6LoWPAN) registers independently with the netstack.
|
||||
No shared mutable god object coupling independent subsystems found.
|
||||
|
||||
### MOAD-0003 (Leaked Context) — CLEAN
|
||||
|
||||
Contiki-NG is an embedded RTOS using cooperative protothreads, not OS threads.
|
||||
No ThreadLocal, pthreads, or task-local storage patterns found. MOAD-0003 does
|
||||
not apply to this architecture.
|
||||
|
||||
### MOAD-0004 (CWE-312) — 1 DEFECT FOUND
|
||||
|
||||
See contiki-0001.
|
||||
|
||||
`os/services/lwm2m/lwm2m-security.c` `write_security_object()` logs the raw LwM2M
|
||||
PSK secret key and PKI public key bytes via `LOG_DBG_COAP_STRING` at `LOG_DBG`
|
||||
level. `LOG_CONF_LEVEL_LWM2M` defaults to `LOG_LEVEL_NONE` in production builds
|
||||
but is runtime-configurable and is set to `LOG_LEVEL_DBG` in example projects
|
||||
(`examples/libs/logging/project-conf.h`). When debug logging is active the
|
||||
device's network authentication credentials are written to UART/serial in
|
||||
cleartext, where they can be captured by gateway nodes and stored in cloud
|
||||
infrastructure.
|
||||
|
||||
### MOAD-0005 (Thundering Herd) — CLEAN
|
||||
|
||||
Contiki-NG uses cooperative scheduling — only one protothread executes at a time.
|
||||
There is no concurrent cache stampede possible within a single node. The SPI bus
|
||||
uses `spi_arch_lock_and_open`/`spi_arch_close_and_unlock` for hardware-level
|
||||
exclusive access. No concurrent get+null+compute+put pattern found.
|
||||
50
defects/trytond/SCAN.md
Normal file
50
defects/trytond/SCAN.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
## Tryton (trytond) 5-MOAD Scan — 2026-03-31
|
||||
|
||||
Target: https://github.com/tryton/trytond (depth=1)
|
||||
Focus: trytond/model/, trytond/ir/, trytond/res/, trytond/protocols/
|
||||
|
||||
### MOAD-0001 (CWE-407) — 1 PRE-EXISTING DEFECT (tryton-0001)
|
||||
|
||||
Tryton uses sets and dicts throughout hot paths. Specific checks:
|
||||
|
||||
- `ir/model.py` `fill_models()`: uses a list for visited-set dedup but the list is
|
||||
bounded by the number of models in a single access check (typically 1-10) and is
|
||||
only called at access-check time, not in a per-record inner loop. Low severity.
|
||||
- `model/tree.py` `check_recursion()`: `visited = set()` throughout. O(1) lookup.
|
||||
- `model/modelsql.py`: field membership checks use dicts/sets. O(1) throughout.
|
||||
- `ir/translation.py`: `trans_reports` and `strings` are dicts. O(1) lookups.
|
||||
- `ir/rule.py`: domain building uses `defaultdict(list)`, no membership scans.
|
||||
- `model/modelstorage.py` `_save_values()`: pre-existing defect — `previous` list
|
||||
O(T*P) in one2many/many2many save. Fixed in tryton-0001.
|
||||
|
||||
### MOAD-0002 (Intertangle) — CLEAN
|
||||
|
||||
`Pool` is our class registry (model, wizard, report types per database). It is
|
||||
intentionally a registry/locator pattern, not a god object coupling subsystems.
|
||||
Modules load independently via `load_modules`. Transaction, Pool, Cache and Model
|
||||
layers are cleanly separated. No shared mutable state coupling unrelated subsystems.
|
||||
|
||||
### MOAD-0003 (Leaked Context) — CLEAN
|
||||
|
||||
`Transaction._local = threading.local()` is an intentional per-thread transaction
|
||||
stack for the WSGI worker thread model. The `_request` context key is explicitly
|
||||
stripped from cache keys (`cache.py` `_key()` strips `_request`). The transaction
|
||||
carries user ID, database name, and context, which are properly scoped to each
|
||||
thread's request lifetime and cleared on `stop()`. This is a standard WSGI
|
||||
thread-per-request pattern, not a leaked identity defect.
|
||||
|
||||
### MOAD-0004 (CWE-312) — CLEAN
|
||||
|
||||
`protocols/dispatcher.py` routes `common.db.login` to its own `login()` function
|
||||
that does NOT call `_safe_repr` or any logger with the `parameters` dict containing
|
||||
the password. `security.login()` logs only username and remote address on success
|
||||
or failure, never the password or session token. No SMTP or LDAP password logged
|
||||
in any logger call found in trytond core.
|
||||
|
||||
### MOAD-0005 (Thundering Herd) — CLEAN
|
||||
|
||||
`MemoryCache` uses a per-transaction `_database_cache` (defaultdict of LRUDict).
|
||||
The transaction isolation model means each transaction sees a consistent snapshot.
|
||||
Cache invalidation uses PostgreSQL `NOTIFY` channels or a polling timeout, not
|
||||
concurrent get+compute+set without synchronization. No unsynchronized
|
||||
cache stampede pattern found.
|
||||
Loading…
Add table
Add a link
Reference in a new issue