結果: **fixed(macOS/POSIX上で検証済み)**。監査で報告した13件に修正と回帰検証を追加しました。以下に元の監査レポートをそのまま保存しています。元レポートの行番号、severity、静的検証に関する記述は、修正前の revision 138e5f822aefff6bf39bb88a89e80be76228a984 を指します。
PRブランチは最新masterの 8fbf1197fc8f75dea0272ed376f8fbbf27a3ca99 に統合しました。この更新に含まれるHTTP EOF/受信エラー修正とESP-IDFの既存ライフサイクルを保持し、追加の長さ検証・失敗時の内容消去・回帰テストを統合しています。
修正と検証の対応
| 元レポート | 修正した境界 | 検証 |
| 1, 2 HTTP受信エラー・EOF | 符号付き受信結果を検査してからオフセットを更新。切断・エラー・長さの不正値を失敗として返し、要求・応答・クライアント送信まで伝播。失敗応答の部分内容を消去 | 固定長・chunkedの部分EOFとEAGAIN、不正長さ、正常な固定長・chunked・接続終了フレーミング、XMLを含む未完了応答の拒否 |
| 3 Objective-Cキャッシュ寿命 | 制御点のロック中に独立したデバイス記述・SCPD・SSDP情報・現在の状態値をコピー。サービス、アクション、状態変数、アイコンが所有元ラッパーを保持 | ASan/UBSanでキャッシュと親ラッパーを破棄してから子ラッパーを参照。名称・状態値・ネイティブリスナーを確認 |
| 4 POSIXスレッド寿命 | runnableと実行終了を分離し、終了通知まで外部停止を待機。自己削除はaction終了後。HTTPクライアントの所有権移譲と排出を同期し、リスナー内のサーバー削除を遅延 | 1秒を超える終了処理、即時停止、再起動、自己削除、HTTPリスナーからのサーバー削除、既存HTTP・UPnPテスト |
| 5 libxml2長さ | 指定されたバイト数をコピーし、int上限と内部NULを検査。UTF-8回復処理の範囲を排他的に制限 | 非NUL終端バッファ、内部NUL、不完全UTF-8、過大な長さ、末尾NULの互換性。libxml2を明示的にリンクしてASan/UBSan検証 |
| 6 TLS検証 | デフォルト信頼ストア・証明書チェーン・DNS名/IP識別子を検証し、DNS SNIを設定。失敗時にTLS状態を解放 | ローカルTLSサーバーで未信頼証明書の拒否、信頼されたlocalhostの成功、IP識別子不一致の拒否 |
| 7 Objective-Cアクションコールバック | ラッパー作成時にraw selfをネイティブリスナーへ登録しない。既存ネイティブリスナーを保持し、ホスト側は公開されたdevice delegateで配送 | ラッパー作成・解放後にも元のネイティブリスナーとuserdataが保持され、呼び出せることを確認 |
| 8 XML深さ | 両バックエンドでノード作成前に128階層の制限を適用し、エラー後の再帰的解放も制限 | 許容境界、境界超過、10,000階層の未完XML、通常の兄弟ノードをExpat/libxml2で検証 |
| 9 SSDP MX | 数字を逐次検査してオーバーフローを避け、正のMXを5秒以内へ制限してから待ち時間を計算 | 0、通常値、6、INT_MAX、非常に長い数値、符号・文字混入 |
| 10 テスト用presentation | 未初期化スタック配列を固定HTMLに置換 | ローカルHTTP要求で応答内容の完全一致を確認 |
| 11 メディア階層循環 | 各走査のコピー済みvisited ID集合、深さ64、要求数1024の制限をC/Objective-C/旧サンプルへ適用 | 実際のCサンプルを取り込むfixtureで自己循環・2ノード循環・正常な有限階層・深さ上限をASan/UBSan検証 |
| 12 SSDP tokenizer | トークンのない入力でも確保済みtokenizerを破棄 | 不正入力1,000回の後に正常ヘッダーを処理。解放経路をソース確認 |
| 13 空UDP | recvfromの負のエラーとゼロ長の成功を区別し、両SSDP受信ワーカーは空データグラムを破棄して続行 | 空UDP直後の正常データグラムを実際のSSDP応答ワーカーで処理 |
検証コマンドと結果
./configure --enable-test --enable-examples CPPFLAGS=-I/opt/homebrew/opt/boost/include LDFLAGS=-L/opt/homebrew/opt/boost/lib: 成功。
make -j4: 成功。既存Autotools生成ファイルの時刻を整えた後、通常のmakeを利用。
make check: 成功。最終カバレッジ実行内でも**38テストケース**を実行し、失敗なし。
./clang-format: 変更したC/C++/Objective-Cファイルを一時コピーに集めて実行し、その結果だけを反映。既存の未追跡ファイルや無関係なソースは対象外。使用したformatterはXcode CommandLineToolsのclang-format。
python3 test/security/verify-macos.py /tmp/mupnp-security-build: 成功。ASan/UBSan、全38ケース、Objective-C寿命、TLS、メディア循環、libxml2の3ケース、SOCKET_DEBUGの2ケースを検証。
git diff --check、Python検証スクリプトの構文コンパイル: 成功。
sh test/espidf/host/check_generators.sh: 成功。-DESP_PLATFORMと既存host shimsで、変更したスレッド・HTTP・ソケット・SSDPソースの構文検証も成功。Linux向けhostテストの実行とESP32実機ビルドは未実施。
- HTTP Content-Lengthの通常の空白・タブを正常対照に加え、ASan/UBSanとSOCKET_DEBUGで再検証: 成功。
Clangとlcovを使った必須カバレッジ検証も成功しました(プロジェクトの行66.9、関数78.1)。macOSでは-lgcovをアーカイブへ追加できないため、CODE_COVERAGE_LIBS=とリンクの--coverageでClangランタイムを使用しました。lcov 1.16は一時ディレクトリに取得し、gcovラッパーはllvm-cov gcov "$@"を実行しました。
PATH=/tmp:/tmp/mupnp-lcov/bin:$PATH ./configure \
--enable-test --enable-examples --enable-code-coverage \
--with-gcov=mupnp-gcov \
CPPFLAGS=-I/opt/homebrew/opt/boost/include \
LDFLAGS=-L/opt/homebrew/opt/boost/lib
PATH=/tmp:/tmp/mupnp-lcov/bin:$PATH make -j4 check-code-coverage \
CODE_COVERAGE_LIBS= \
LDFLAGS='-L/opt/homebrew/opt/boost/lib --coverage' \
CODE_COVERAGE_LCOV_OPTIONS='--gcov-tool /tmp/mupnp-gcov --base-directory /Users/skonno/Src/mupnp/test/unix' \
CODE_COVERAGE_LCOV_RMOPTS= \
CODE_COVERAGE_IGNORE_PATTERN="'/Applications/*' '/opt/*'"
修正前のHTTP実装だけを差し替えた同じfixtureでは、部分本文受信後のEOFが4秒のタイムアウトまで終了しませんでした。修正後はEOF/EAGAINを失敗として即座に返し、正常な固定長・chunked・接続終了本文は従来どおり取得できました。深いXML、キャッシュ破棄後の子参照、循環するメディア階層、TLSの正常・異常の各対照も上記fixtureで検証しています。
独立した修正前調査と、1回の候補レビューを実施しました。レビューで確認した受信バッファの余分な終端領域、クライアント応答エラー伝播、HTTPリスナー内削除の問題を修正し、対応するテストを追加しています。
PRのCI指摘への対応
PR #28の初回CIではLinuxビルド、ESP-IDFビルド、host-regressionが成功しました。SonarCloudがSOCKET_DEBUGで1バイトの読み捨て先に終端NULを書き込む既存の境界問題を検出したため、受信バッファを書き換えない長さ指定ログへ変更しました。長いHTTPヘッダーを正常対照に追加し、SOCKET_DEBUGとASan/UBSanで検証しました。メディアサンプル3箇所は examples/common/content_directory.h の単一の走査・予算処理を利用し、循環防止の重複と複雑度を解消しています。共通ヘッダーはAutotoolsの配布対象にも追加しました。更新後の38ケース、カバレッジ、サニタイザーfixtureは成功しています。
マージ後の寿命管理の追補
PR #28は 8e321fe1f95242671874a43cb5ae9638166f3115 としてマージされました。最終確認で見つかった未反映の2箇所を、元レポート4の追補修正としてまとめています。
- 実行中のデバイスを直接削除すると、記述・サービスを解放してから所有ワーカーを停止していました。advertiser、HTTP、SSDPワーカーを先に停止し、共有データを使う処理の終了を待ってから解放します。
- HTTPクライアントがサーバーの管理リストから外れた直後、外部削除でサーバーが解放される可能性がありました。遅延削除フラグを所有権移譲と同じロック内で取得し、その後の参照をなくしました。
回帰検証に、再起動したデバイスの直接削除と、要求直後のHTTPサーバーの反復削除・再作成を追加しました。ASan/UBSanの全38ケース、Objective-C寿命、TLS、メディア循環、libxml2、SOCKET_DEBUG検証は成功しました。ESP_PLATFORM定義で変更した2つのCソースの構文検証も成功しています。 HTTPの反復再作成はポート再利用の待ち時間と分離するため、再作成ごとに別のポートを使用します。
追補の最終 make check-code-coverage 内の通常38ケースは失敗なし、行66.9(6007/8977)、関数78.1(634/812)でした。途中の実行では既存Subscriptionのイベント受信待ちが1回失敗しましたが、ASan/UBSan全体とその後の単独カバレッジ実行では成功しました。
互換性と検証範囲
- 制御点が返すObjective-Cデバイスは独立したスナップショットです。取得済みスナップショットへの後続キャッシュイベントの自動反映はありません。最新情報は再取得してください。サービスURL、SCPD、状態値、子デバイスを保持し、アクション送信は既存のネイティブAPIを利用します。
- POSIXのstopはaction終了まで待ちます。actionの必要とするロックを保持して外部stopを呼ばないでください。HTTPサーバーの自己削除は検証していますが、アプリケーションが任意の所有デバイスを実行中のコールバックから破棄する一般的な寿命管理まで保証する変更ではありません。
- TLSはOpenSSL 1.0.2以上の識別子検証APIを使用します。それ以前のTLSバックエンドは検証できないため接続を拒否します。
- iOS、Windows、組込みOS、歴史的Xcode/CyberLinkプロジェクトは実行していません。macOSのObjective-Cラッパー、ネイティブPOSIX、明示的なOpenSSL/libxml2変種を検証しています。Cメディアサンプルの同等の制限を旧C/Objective-Cサンプルにも適用しましたが、その旧ビルドの実行は未検証です。
- macOSのASan/UBSanを使用し、LeakSanitizerの計測は実施していません。tokenizerの解放は早期returnのソース確認と反復・正常対照テストで確認しました。
- 既存のバージョン定義の重複や非推奨APIに関する警告は残っています。監査の未確認候補や未監査領域は、下記の元レポートに記載された範囲を維持します。
Security Review: mupnp
Scope
Single-pass static security review of current mUPnP project: native C library and public headers, Objective-C wrappers, runnable examples, tests and legacy AV executable source bodies.
- Scan mode: repository
- Target kind: git_worktree
- Target ID: target_sha256_ac520384bebbff49113ae7d3a8e93534ea782b04f5e6b842337b312b208f3cde
- Revision: 138e5f822aefff6bf39bb88a89e80be76228a984
- Snapshot digest: codex-security-snapshot/v1:sha256:d25a037685a81bb2e3c05f3fa3f7e217e5725f470a6e8c54c27964e9f33ed061
- Inventory strategy: repository
- Included paths: .
- Excluded paths: none
- Scan context: Check the project
Limitations and exclusions:
- No builds, runtime PoCs, test execution, live services, network access, Git history or external vulnerability databases were used.
- 229 implementation/API files fully reviewed; 10 additional files had executable-body review with fixed XML/HTML literals excluded.
- Coverage is partial for historical project configurations, ancillary declarations, fixed literal sections and generated/binary artifacts; a legacy YouTube HTTP candidate remains deferred pending build/reachability evidence.
- One parser investigator final response was blocked by the security access filter. Earlier observations were independently validated from source; its source-coverage-only response was available.
- Excluded doc/**, build/**, generated documentation, object/dependency files, bundled binaries/images: Non-product generated or binary artifacts were excluded from the static implementation audit.
- Excluded examples/binarylight/binarylight_device.c, examples/clock/clock_device.c, test/TestDevice.c, std/av/src/cybergarage/upnp/std/av/renderer/cavtransport_service.c, std/av/src/cybergarage/upnp/std/av/renderer/cconnectionmgrr_service.c, std/av/src/cybergarage/upnp/std/av/renderer/cmediarenderer_device.c, std/av/src/cybergarage/upnp/std/av/renderer/crenderingcontrol_service.c, std/av/src/cybergarage/upnp/std/av/server/cconnectionmgr_service.c, std/av/src/cybergarage/upnp/std/av/server/ccontentdir_service.c, std/av/src/cybergarage/upnp/std/av/server/cmediaserver_device.c: Executable bodies were audited; large trusted fixed XML/HTML literal sections were not exhaustively reviewed.
- Excluded Historical Xcode/Visual Studio project configurations and ancillary platform UI headers: Build routes and relevant flags inspected selectively; not every historical configuration/declaration was fully reviewed.
Scan Summary
| Field | Value |
| Scan outcome | completed |
| Reportable findings | 13 |
| Severity mix | high: 1, medium: 7, low: 5 |
| Confidence mix | high: 13 |
| Coverage | partial |
| Validation mode | static source trace |
Canonical artifacts: scan-manifest.json, findings.json, and coverage.json. This report is a deterministic projection of those files.
Threat Model
mUPnP is an embeddable C library for UPnP devices and control points, with Objective-C wrappers and demonstration applications. Device startup creates HTTP and SSDP listeners and announces a device; control-point startup creates SSDP multicast/unicast and HTTP event listeners, discovers devices, fetches XML descriptions and SCPDs, and permits application-directed SOAP control. It runs with the embedding process's native memory, filesystem and network authority, rather than as an isolated service. Source: src/mupnp/device.c:1373-1419; src/mupnp/controlpoint.c:164-225; src/mupnp/controlpoint.c:585-624; src/mupnp/control/action_ctrl.c:85-110; wrapper/objc/mUPnP/CGUpnpControlPoint.m:28; wrapper/objc/mUPnP/CGUpnpControlPoint.m:51. Examples and tests are distinct from the library: CMakeLists.txt:144-154; examples/clock/clock_device.c:262-282.
Assets
- Integrity and availability of the embedding native process and its application-owned device action callbacks. Network input is parsed inside this process and action listeners execute directly: src/mupnp/http/http_server.c:182-200; src/mupnp/control/action_ctrl.c:54-68; src/mupnp/xml/xml_parser_expat.c:178-195.
- Device and service XML descriptions, action arguments, discovered-device cache, state variables and subscription identifiers. Received notifications update state and call application listeners: src/mupnp/controlpoint_http_server.c:53-101.
- Host network authority and evented service values sent to subscriber-selected destinations. CALLBACK supplies the stored delivery URL; notification host, port and path come from that URL: src/mupnp/device_http_server.c:530-551; src/mupnp/event/subscriber.c:166-171; src/mupnp/event/notify_request.c:124-125; src/mupnp/event/notify_request.c:160-176.
- Optional local description-file reads and general file operations retain process filesystem authority; filenames are caller supplied: src/mupnp/device.c:286-307; src/mupnp/io/file.c:374-392; src/mupnp/io/file.c:439-455.
- Conditional CI credential and publication authority: CODECOV_TOKEN is passed to the Codecov action; GITHUB_TOKEN is passed to the GitHub Pages deployment action for ./doc/html. Source: .github/workflows/make.yml:34-40; .github/workflows/doxygen.yml:39-45.
Trust Boundaries
- Network peers -> device HTTP listener -> parser and application actions. The listener binds configured host interfaces at port 38400 by default, incrementing on bind failure; MUPNP_NET_USE_ANYADDR changes the bind address to 0.0.0.0. GET/HEAD, POST, subscription and presentation requests have separate dispatch; custom HTTP/presentation/action listeners belong to the embedding application. Service URL and action routing are protocol controls, not actor authentication. Source: include/mupnp/device.h:56-59; src/mupnp/device.c:1388-1399; src/mupnp/http/http_server_list.c:71-84; src/mupnp/net/interface.c:68-81; src/mupnp/device_http_server.c:68-89; src/mupnp/device_http_server.c:318-356; src/mupnp/control/action_ctrl.c:54-68.
- SSDP peers -> control-point discovery -> host HTTP egress -> XML model. SSDP LOCATION supplies the description URL; only HTTP URLs proceed through the description consumer, which uses the URL host/port/request and checks response success before parsing. SCPDURL is resolved as an absolute URL, against XML URLBase, or against the SSDP LOCATION directory. Source: src/mupnp/controlpoint.c:594-618; src/mupnp/device.c:248-273; src/mupnp/service.c:179-181; src/mupnp/service.c:1099-1178; src/mupnp/service.c:1196-1208; src/mupnp/service.c:254-282.
- Application-directed control -> description-derived device endpoint. mupnp_action_post constructs and sends a SOAP request; the service control URL determines destination host/port and SOAP action arguments are serialized from application action objects. Device-description metadata remains peer supplied. Source: src/mupnp/control/action_ctrl.c:93-101; src/mupnp/control/action_request.c:229-248; src/mupnp/control/action_request.c:271-278; src/mupnp/service.c:168-170.
- Network subscriber -> device subscription state -> outbound notifications. The built-in handler checks event-subscription service URI, CALLBACK/SID/NT combinations and NT value, creates a SID, clamps timeout when a positive maximum is configured, and stores CALLBACK after angle-bracket trimming. Host/port/path consumers are independent of incoming peer address. Source: src/mupnp/device_http_server.c:465-511; src/mupnp/device_http_server.c:530-560; src/mupnp/event/subscriber.c:166-171; src/mupnp/event/notify_request.c:124-125.
- Network event sender -> control-point HTTP callback -> cached state and application observers. Callback is published using the selected interface, actual event port and /eventSub; defaults start at TCP 39500 and may increment. Built-in handling matches SID to a known service and checks sequence before applying properties and notifying listeners. Source: include/mupnp/controlpoint.h:49-52; src/mupnp/controlpoint.c:51-56; src/mupnp/controlpoint.c:185-197; src/mupnp/controlpoint_http_server.c:49-101.
- Caller-selected local file -> optional XML parser. MUPNP_USE_CFILE gates description-file loading; no repository directory confinement is implied. The actual filename is passed through to fopen with process OS permissions. Source: src/mupnp/device.c:286-307; src/mupnp/io/file.c:374-392.
- Trusted source/build configuration -> produced native library. Autoconf emits MUPNP_* consumer macros. CMake advertises CG_* options and emits CG_* definitions instead, so those switches alone do not enable consumer paths guarded by MUPNP_*. Source: CMakeLists.txt:9-24; configure.ac:128-149; configure.ac:279-319; src/mupnp/http/http_server_list.c:72-77; src/mupnp/xml/xml_parser_expat.c:22; src/mupnp/xml/xml_parser_libxml2.c:22; include/mupnp/net/socket.h:18.
- Repository source -> CI runner and conditional external reporting/publication. Build CI triggers on master pushes and pull requests and executes repository bootstrap/configure/make; documentation publication triggers on master pushes and supplies GITHUB_TOKEN to the deployment action. Source: .github/workflows/make.yml:3-30; .github/workflows/doxygen.yml:7-9; .github/workflows/doxygen.yml:39-45.
Attacker Capabilities
- A peer with network reachability can submit SSDP packets, device HTTP/SOAP/subscription requests and control-point notifications. Exact reachability depends on host interfaces, firewall and embedding deployment; LAN-only isolation is not enforced merely by the README's home-network description. Source: src/mupnp/ssdp/ssdp_server.c:65-78; src/mupnp/http/http_server_list.c:71-84; src/mupnp/device_http_server.c:76-89.
- An advertised device can control LOCATION, XML URLBase, service URLs and HTTP description/SCPD contents, which cross into control-point HTTP egress and native XML parsing. This actor is not assumed to control the embedding application, OS account or trusted build flags. Source: src/mupnp/controlpoint.c:594-618; src/mupnp/service.c:1196-1208.
- A network subscriber supplies CALLBACK and timeout to the built-in device eventing path. New authority from a boundary failure would need to exceed the intended ability to receive evented service data at a callback; deployment-specific internal destinations and sensitive event values remain prerequisites. Source: src/mupnp/device_http_server.c:530-560.
- A caller can supply parser buffers, action objects, listener callbacks and optional filenames through the public C API. Callers normally already possess the same native process authority; caller choices are not themselves privilege escalation. Source: src/mupnp/device.c:288-307; src/mupnp/control/action_ctrl.c:85-110.
- A pull-request author controls submitted source for CI execution but is not assumed to possess maintainer merge rights, repository secrets or master-only publication authority. Source: .github/workflows/make.yml:3-7; .github/workflows/doxygen.yml:7-9.
Security Objectives
- Preserve embedding-process memory safety and availability while parsing network HTTP, SSDP and XML input; enforce bounds before native allocations and updates. Source consumers: src/mupnp/http/http_packet.c:450-473; src/mupnp/xml/xml_parser_expat.c:178-195.
- Preserve application-defined control policy when network requests route to presentation, action and query callbacks; callers must understand that these callbacks run with process authority. Source: src/mupnp/device_http_server.c:68-83; src/mupnp/control/action_ctrl.c:54-68.
- Keep outgoing description, control and notification destinations bound to the intended peer or application policy, including host, port and derived request path. Source: src/mupnp/device.c:251-261; src/mupnp/service.c:1099-1208; src/mupnp/event/subscriber.c:166-171; src/mupnp/event/notify_request.c:124-125.
- Apply notifications only to the intended known subscription and preserve coherent service state; existing component controls include SID matching and sequence checks. Source: src/mupnp/controlpoint_http_server.c:53-89.
- Ensure build configuration establishes the advertised network, file, TLS and XML backend behavior at actual consumers, and preserve CI credential and publication target separation. Source: CMakeLists.txt:9-24; configure.ac:279-319; .github/workflows/make.yml:34-40; .github/workflows/doxygen.yml:39-45.
Assumptions
- User context: 'Check the project'; no supplied threat model, deployment knowledge base or root SECURITY.md. Root policy resolution returned no policy. This architecture pass is read-only source mapping and does not constitute security-audit coverage.
- Actual embedding applications, network exposure, sensitive device actions/event values, OS privileges, runtime library versions and effective compile flags are unspecified. No multi-tenant boundary, credential store, admin service or sandbox should be invented.
- CMake configuration discrepancy: CG_* advertised options are emitted as CG_* definitions, but inspected consumers use MUPNP_* gates; no source alias was found. Therefore CMake option values do not demonstrate ANYADDR, TLS, CFILE or XML backend activation. Autoconf uses the expected MUPNP_* macros. Source: CMakeLists.txt:9-24; configure.ac:128-149; configure.ac:279-319; src/mupnp/device.c:286; src/mupnp/http/http_server_list.c:72; include/mupnp/net/socket.h:18; src/mupnp/xml/xml_parser_libxml2.c:22.
- Autoconf –enable-anyaddr help claims default yes, but the definition is added only in the provided-option yes branch; omission supplies no default action. Without external flags, omitted option follows per-interface consumers. Source: configure.ac:284-294; src/mupnp/http/http_server_list.c:72-77.
- Control-point API documentation says required ports may already be in use, while startup increments HTTP event port without an explicit bound and retries SSDP response ports through configured start +80. Effective listener/callback ports must be read after startup. Source: include/mupnp/controlpoint.h:219-224; src/mupnp/controlpoint.c:185-216.
- Expat is selected by its source guard when HAVE_CONFIG_H and TARGET_OS_IPHONE are absent; libxml2 has a different platform guard. Runtime parser version and entity/network behavior require later source/dependency validation, not assumptions from package names. Source: src/mupnp/xml/xml_parser_expat.c:22; src/mupnp/xml/xml_parser_libxml2.c:22; CMakeLists.txt:190-207.
- Objective-C wrappers use the same C objects/start APIs, and Unix/Windows clock startup uses the same device API; no distinct isolation boundary is established by wrappers or sample platform projects. Source: wrapper/objc/mUPnP/CGUpnpControlPoint.m:28; wrapper/objc/mUPnP/CGUpnpControlPoint.m:51; examples/clock/unix/clock_main.c:52; examples/clock/win32/vs2005/clock_main_win32.c:37.
Findings
| Finding | Severity | Confidence | Detailed write-up |
| HTTP read errors become negative heap offsets | high | high | inline below |
| Truncated HTTP body produces an infinite CPU loop | medium | high | inline below |
| Objective-C device wrappers retain pointers freed by SSDP removal | medium | high | inline below |
| POSIX worker shutdown frees live thread and server state | medium | high | inline below |
| Libxml2 parsing uses a byte length larger than its input copy | medium | high | inline below |
| Native HTTPS accepts unverified peer certificates | medium | high | inline below |
| Action wrappers leave a dangling Objective-C callback target | medium | high | inline below |
| Deep XML input can exhaust the stack during tree cleanup | medium | high | inline below |
| Unbounded M-SEARCH MX blocks the SSDP worker | low | high | inline below |
| Test presentation handler sends uninitialized stack contents | low | high | inline below |
| Media directory dump follows container cycles without a bound | low | high | inline below |
| Malformed SSDP packets leak tokenizers without bound | low | high | inline below |
| A zero-length UDP packet permanently stops SSDP reception | low | high | inline below |
Confidence Scale
| Label | Meaning |
| high | Direct evidence supports the finding with no material unresolved blocker. |
| medium | Evidence supports a plausible issue, but material runtime or reachability proof remains. |
| low | Evidence is incomplete and the item is retained only for explicit follow-up. |
[1] HTTP read errors become negative heap offsets
| Field | Value |
| Severity | high |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | memory-safety |
| CWE | CWE-787 |
| Affected lines | src/mupnp/http/http_request.c:740-745, src/mupnp/http/http_packet.c:462-468, src/mupnp/http/http_packet.c:414-422, src/mupnp/net/socket.c:701-713 |
Summary
A TCP peer delays a declared body past receive timeout; recv returns -1 which decreases readLen and the next receive pointer precedes the allocation. Native request and response readers share this code; chunk reader has the same operation. Receive timeout and retry limit do not preserve a nonnegative offset.
Root Cause
A TCP peer delays a declared body past receive timeout; recv returns -1 which decreases readLen and the next receive pointer precedes the allocation. Native request and response readers share this code; chunk reader has the same operation. Receive timeout and retry limit do not preserve a nonnegative offset.
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
http chunk — src/mupnp/http/http_packet.c:414-422
The chunk reader shares the signed-offset error and appends the accumulated result.
readLen = 0;
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
tries++;
}
mupnp_http_packet_appendncontent(httpPkt, content, readLen);
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Validation
Parent source review confirms the input-to-control-to-outcome path. A TCP peer delays a declared body past receive timeout; recv returns -1 which decreases readLen and the next receive pointer precedes the allocation. Native request and response readers share this code; chunk reader has the same operation. Receive timeout and retry limit do not preserve a nonnegative offset.
Validation method: static source trace
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
http chunk — src/mupnp/http/http_packet.c:414-422
The chunk reader shares the signed-offset error and appends the accumulated result.
readLen = 0;
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
tries++;
}
mupnp_http_packet_appendncontent(httpPkt, content, readLen);
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
http-input -> http-read -> http-chunk -> socket-recv
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
http chunk — src/mupnp/http/http_packet.c:414-422
The chunk reader shares the signed-offset error and appends the accumulated result.
readLen = 0;
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
tries++;
}
mupnp_http_packet_appendncontent(httpPkt, content, readLen);
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Reachability
Timing requires one receive error followed by more peer-controlled data; reachable on exposed native HTTP listeners.
Severity
High — Timing requires one receive error followed by more peer-controlled data; reachable on exposed native HTTP listeners.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: Timing requires one receive error followed by more peer-controlled data; reachable on exposed native HTTP listeners.
Likelihood assessment:
- Level: high
- Why: Timing requires one receive error followed by more peer-controlled data; reachable on exposed native HTTP listeners.
Remediation
Keep receive result separate; fail and free on nonpositive result; accumulate only positive bounded reads.
Tests:
- Keep receive result separate; fail and free on nonpositive result; accumulate only positive bounded reads. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[2] Truncated HTTP body produces an infinite CPU loop
| Field | Value |
| Severity | medium |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | denial-of-service |
| CWE | CWE-835 |
| Affected lines | src/mupnp/http/http_request.c:740-745, src/mupnp/http/http_packet.c:462-468, src/mupnp/net/socket.c:701-713 |
Summary
After positive partial body data, EOF repeatedly returns zero. Accumulated readLen stays positive so tries does not increase. Timeouts do not bound immediate EOF and runnableFlag is never checked inside the loop. Repeated connections consume CPU and workers.
Root Cause
After positive partial body data, EOF repeatedly returns zero. Accumulated readLen stays positive so tries does not increase. Timeouts do not bound immediate EOF and runnableFlag is never checked inside the loop. Repeated connections consume CPU and workers.
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Validation
Parent source review confirms the input-to-control-to-outcome path. After positive partial body data, EOF repeatedly returns zero. Accumulated readLen stays positive so tries does not increase. Timeouts do not bound immediate EOF and runnableFlag is never checked inside the loop. Repeated connections consume CPU and workers.
Validation method: static source trace
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
http-input -> http-read -> socket-recv
http input — src/mupnp/http/http_request.c:740-745
The network request parser invokes the shared body reader when framing headers are present.
mupnp_http_packet_read_headers((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
if (mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_CONTENT_LENGTH) || mupnp_http_packet_hasheader((mUpnpHttpPacket*)httpReq, MUPNP_HTTP_TRANSFER_ENCODING))
mupnp_http_packet_read_body((mUpnpHttpPacket*)httpReq, sock, lineBuf, sizeof(lineBuf));
http read — src/mupnp/http/http_packet.c:462-468
A signed receive result changes the accumulated byte offset before it is checked.
while (readLen < conLen && tries < 20) {
readLen += mupnp_socket_read(sock, (content + readLen), (conLen - readLen));
if (readLen <= 0)
tries++;
}
socket recv — src/mupnp/net/socket.c:701-713
Native recv/SSL_read returns signed error or EOF values directly.
recvLen = so_recv(sock->id, buffer, bufferLen, 0);
#elif defined(TENGINE) && defined(MUPNP_TENGINE_NET_KASAGO)
recvLen = ka_recv(sock->id, buffer, bufferLen, 0);
#elif defined(ITRON)
recvLen = tcp_rcv_dat(sock->id, buffer, bufferLen, TMO_FEVR);
#else
recvLen = recv(sock->id, buffer, bufferLen, 0);
#endif
#if defined(MUPNP_USE_OPENSSL)
}
else {
recvLen = SSL_read(sock->ssl, buffer, bufferLen);
Reachability
A network peer can retain a worker in a busy loop; repeated connections affect service availability.
Severity
Medium — A network peer can retain a worker in a busy loop; repeated connections affect service availability.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: medium
- Why: A network peer can retain a worker in a busy loop; repeated connections affect service availability.
Likelihood assessment:
- Level: high
- Why: A network peer can retain a worker in a busy loop; repeated connections affect service availability.
Remediation
Stop immediately on EOF before completing the declared body.
Tests:
- Stop immediately on EOF before completing the declared body. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[3] Objective-C device wrappers retain pointers freed by SSDP removal
Summary
Control-point enumeration returns retainable Objective-C wrappers around borrowed cache-device pointers. SSDP removal deletes the C device without invalidating those wrappers. Later property and subordinate-object access dereferences freed memory; the iOS browser retains and later reads such wrappers.
Root Cause
Control-point enumeration returns retainable Objective-C wrappers around borrowed cache-device pointers. SSDP removal deletes the C device without invalidating those wrappers. Later property and subordinate-object access dereferences freed memory; the iOS browser retains and later reads such wrappers.
device byebye — src/mupnp/controlpoint.c:863-867
A peer departure notification enters cache removal.
if (mupnp_ssdp_packet_isrootdevice(ssdpPkt) == true) {
if (mupnp_ssdp_packet_isalive(ssdpPkt) == true)
mupnp_controlpoint_adddevicebyssdppacket(ctrlPoint, ssdpPkt);
else if (mupnp_ssdp_packet_isbyebye(ssdpPkt) == true)
mupnp_controlpoint_removedevicebyssdppacket(ctrlPoint, ssdpPkt);
objc list — wrapper/objc/mUPnP/CGUpnpControlPoint.m:103-113
Enumeration creates externally retainable wrappers of live cache pointers.
-c
return [NSArray array];
NSMutableArray* devArray = [NSMutableArray array];
mUpnpDevice* cDevice;
for (cDevice = mupnp_controlpoint_getdevices(cObject); cDevice; cDevice = mupnp_device_next(cDevice)) {
CGUpnpDevice* device = [[[CGUpnpDevice alloc] initWithCObject:cDevice] autorelease];
[devArray addObject:device];
}
return devArray;
}
- (CGUpnpDevice*)deviceForUDN:(NSString*)udn
objc borrow — wrapper/objc/mUPnP/CGUpnpDevice.m:41-48
The wrapper stores a borrowed pointer without extending or tracking its lifetime.
-c
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
isCObjectCreated = NO;
return self;
}
device remove — src/mupnp/controlpoint.c:717-725
The underlying C device is deleted after the notification; wrappers are not invalidated.
if (listener != NULL) {
mupnp_controlpoint_unlock(ctrlPoint);
listener(ctrlPoint, udn, mUpnpDeviceStatusRemoved);
mupnp_controlpoint_lock(ctrlPoint);
}
mupnp_device_delete(dev);
mupnp_controlpoint_unlock(ctrlPoint);
objc read — wrapper/objc/mUPnP/CGUpnpDevice.m:78-86
Non-NULL dangling cObject is dereferenced by ordinary property access.
-c
- (NSString*)friendlyName
{
if (!cObject)
return nil;
const char* name = mupnp_device_getfriendlyname(cObject);
if (name) {
return [NSString stringWithUTF8String:name];
}
return nil;
Validation
Control-point enumeration returns retainable Objective-C wrappers around borrowed cache-device pointers. SSDP removal deletes the C device without invalidating those wrappers. Later property and subordinate-object access dereferences freed memory; the iOS browser retains and later reads such wrappers.
Validation method: static source trace
device byebye — src/mupnp/controlpoint.c:863-867
A peer departure notification enters cache removal.
if (mupnp_ssdp_packet_isrootdevice(ssdpPkt) == true) {
if (mupnp_ssdp_packet_isalive(ssdpPkt) == true)
mupnp_controlpoint_adddevicebyssdppacket(ctrlPoint, ssdpPkt);
else if (mupnp_ssdp_packet_isbyebye(ssdpPkt) == true)
mupnp_controlpoint_removedevicebyssdppacket(ctrlPoint, ssdpPkt);
objc list — wrapper/objc/mUPnP/CGUpnpControlPoint.m:103-113
Enumeration creates externally retainable wrappers of live cache pointers.
-c
return [NSArray array];
NSMutableArray* devArray = [NSMutableArray array];
mUpnpDevice* cDevice;
for (cDevice = mupnp_controlpoint_getdevices(cObject); cDevice; cDevice = mupnp_device_next(cDevice)) {
CGUpnpDevice* device = [[[CGUpnpDevice alloc] initWithCObject:cDevice] autorelease];
[devArray addObject:device];
}
return devArray;
}
- (CGUpnpDevice*)deviceForUDN:(NSString*)udn
objc borrow — wrapper/objc/mUPnP/CGUpnpDevice.m:41-48
The wrapper stores a borrowed pointer without extending or tracking its lifetime.
-c
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
isCObjectCreated = NO;
return self;
}
device remove — src/mupnp/controlpoint.c:717-725
The underlying C device is deleted after the notification; wrappers are not invalidated.
if (listener != NULL) {
mupnp_controlpoint_unlock(ctrlPoint);
listener(ctrlPoint, udn, mUpnpDeviceStatusRemoved);
mupnp_controlpoint_lock(ctrlPoint);
}
mupnp_device_delete(dev);
mupnp_controlpoint_unlock(ctrlPoint);
objc read — wrapper/objc/mUPnP/CGUpnpDevice.m:78-86
Non-NULL dangling cObject is dereferenced by ordinary property access.
-c
- (NSString*)friendlyName
{
if (!cObject)
return nil;
const char* name = mupnp_device_getfriendlyname(cObject);
if (name) {
return [NSString stringWithUTF8String:name];
}
return nil;
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
device-byebye -> objc-list -> objc-borrow -> device-remove -> objc-read
device byebye — src/mupnp/controlpoint.c:863-867
A peer departure notification enters cache removal.
if (mupnp_ssdp_packet_isrootdevice(ssdpPkt) == true) {
if (mupnp_ssdp_packet_isalive(ssdpPkt) == true)
mupnp_controlpoint_adddevicebyssdppacket(ctrlPoint, ssdpPkt);
else if (mupnp_ssdp_packet_isbyebye(ssdpPkt) == true)
mupnp_controlpoint_removedevicebyssdppacket(ctrlPoint, ssdpPkt);
objc list — wrapper/objc/mUPnP/CGUpnpControlPoint.m:103-113
Enumeration creates externally retainable wrappers of live cache pointers.
-c
return [NSArray array];
NSMutableArray* devArray = [NSMutableArray array];
mUpnpDevice* cDevice;
for (cDevice = mupnp_controlpoint_getdevices(cObject); cDevice; cDevice = mupnp_device_next(cDevice)) {
CGUpnpDevice* device = [[[CGUpnpDevice alloc] initWithCObject:cDevice] autorelease];
[devArray addObject:device];
}
return devArray;
}
- (CGUpnpDevice*)deviceForUDN:(NSString*)udn
objc borrow — wrapper/objc/mUPnP/CGUpnpDevice.m:41-48
The wrapper stores a borrowed pointer without extending or tracking its lifetime.
-c
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
isCObjectCreated = NO;
return self;
}
device remove — src/mupnp/controlpoint.c:717-725
The underlying C device is deleted after the notification; wrappers are not invalidated.
if (listener != NULL) {
mupnp_controlpoint_unlock(ctrlPoint);
listener(ctrlPoint, udn, mUpnpDeviceStatusRemoved);
mupnp_controlpoint_lock(ctrlPoint);
}
mupnp_device_delete(dev);
mupnp_controlpoint_unlock(ctrlPoint);
objc read — wrapper/objc/mUPnP/CGUpnpDevice.m:78-86
Non-NULL dangling cObject is dereferenced by ordinary property access.
-c
- (NSString*)friendlyName
{
if (!cObject)
return nil;
const char* name = mupnp_device_getfriendlyname(cObject);
if (name) {
return [NSString stringWithUTF8String:name];
}
return nil;
Reachability
Requires Objective-C wrapper use across asynchronous removal. Core cache locking does not protect external wrapper getters; an application that never retains wrappers across removal avoids the specific trigger. Arbitrary code execution was not established.
Severity
Medium — Requires Objective-C wrapper use across asynchronous removal. Core cache locking does not protect external wrapper getters; an application that never retains wrappers across removal avoids the specific trigger. Arbitrary code execution was not established.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: Control-point enumeration returns retainable Objective-C wrappers around borrowed cache-device pointers. SSDP removal deletes the C device without invalidating those wrappers. Later property and subordinate-object access dereferences freed memory; the iOS browser retains and later reads such wrappers.
Likelihood assessment:
- Level: medium
- Why: Requires Objective-C wrapper use across asynchronous removal. Core cache locking does not protect external wrapper getters; an application that never retains wrappers across removal avoids the specific trigger. Arbitrary code execution was not established.
Remediation
Give wrappers refcounted native ownership or a shared invalidatable handle checked under the control-point lock; share that lifetime control with service, action, state-variable and icon wrappers.
Tests:
- Retain a device and subordinate wrapper across simulated cache removal and confirm every later access returns safely.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[4] POSIX worker shutdown frees live thread and server state
| Field | Value |
| Severity | medium |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | use-after-free |
| CWE | CWE-416 |
| Affected lines | src/mupnp/http/http_server.c:320-330, src/mupnp/util/thread.c:451-463, src/mupnp/util/thread.c:262-268, src/mupnp/http/http_server.c:218-230 |
Summary
Detached worker stop sets runnableFlag, probes liveness with signal 0 and sleeps instead of waiting for completion. thread_delete frees state. Delayed HTTP worker resumes during legitimate shutdown and reads freed thread/server state; requires lifecycle transition. Sleeping is not lifetime synchronization.
Root Cause
Detached worker stop sets runnableFlag, probes liveness with signal 0 and sleeps instead of waiting for completion. thread_delete frees state. Delayed HTTP worker resumes during legitimate shutdown and reads freed thread/server state; requires lifecycle transition. Sleeping is not lifetime synchronization.
http stop — src/mupnp/http/http_server.c:320-330
Server stop destroys the worker list following asynchronous stop.
if (httpServer->acceptThread != NULL) {
mupnp_thread_stop(httpServer->acceptThread);
mupnp_thread_delete(httpServer->acceptThread);
httpServer->acceptThread = NULL;
}
if (httpServer->clientThreads != NULL) {
mupnp_threadlist_stop(httpServer->clientThreads);
mupnp_threadlist_delete(httpServer->clientThreads);
httpServer->clientThreads = NULL;
}
thread stop — src/mupnp/util/thread.c:451-463
Signal zero and a sleep do not establish worker exit.
#elif defined(TENGINE) && defined(PROCESS_BASE)
b_ter_tsk(thread->taskID);
#else
mupnp_log_debug_s("Killing thread %p\n", thread);
pthread_kill(thread->pThread, 0);
mupnp_sleep(MUPNP_THREAD_MIN_SLEEP);
#endif
thread free — src/mupnp/util/thread.c:262-268
Thread storage is freed without waiting for completion.
if (thread->runnableFlag == true)
mupnp_thread_stop(thread);
mupnp_thread_remove(thread);
free(thread);
http cleanup — src/mupnp/http/http_server.c:218-230
A returning worker removes/frees its thread and accesses the owner mutex.
mupnp_socket_close(clientSock);
mupnp_socket_delete(clientSock);
mupnp_http_server_clientdata_delete(clientData);
mupnp_thread_setuserdata(thread, NULL);
mupnp_http_server_lock(httpServer);
mupnp_thread_remove(thread);
mupnp_http_server_unlock(httpServer);
mupnp_log_debug_l4("Leaving...\n");
Validation
Parent source review confirms the input-to-control-to-outcome path. Detached worker stop sets runnableFlag, probes liveness with signal 0 and sleeps instead of waiting for completion. thread_delete frees state. Delayed HTTP worker resumes during legitimate shutdown and reads freed thread/server state; requires lifecycle transition. Sleeping is not lifetime synchronization.
Validation method: static source trace
http stop — src/mupnp/http/http_server.c:320-330
Server stop destroys the worker list following asynchronous stop.
if (httpServer->acceptThread != NULL) {
mupnp_thread_stop(httpServer->acceptThread);
mupnp_thread_delete(httpServer->acceptThread);
httpServer->acceptThread = NULL;
}
if (httpServer->clientThreads != NULL) {
mupnp_threadlist_stop(httpServer->clientThreads);
mupnp_threadlist_delete(httpServer->clientThreads);
httpServer->clientThreads = NULL;
}
thread stop — src/mupnp/util/thread.c:451-463
Signal zero and a sleep do not establish worker exit.
#elif defined(TENGINE) && defined(PROCESS_BASE)
b_ter_tsk(thread->taskID);
#else
mupnp_log_debug_s("Killing thread %p\n", thread);
pthread_kill(thread->pThread, 0);
mupnp_sleep(MUPNP_THREAD_MIN_SLEEP);
#endif
thread free — src/mupnp/util/thread.c:262-268
Thread storage is freed without waiting for completion.
if (thread->runnableFlag == true)
mupnp_thread_stop(thread);
mupnp_thread_remove(thread);
free(thread);
http cleanup — src/mupnp/http/http_server.c:218-230
A returning worker removes/frees its thread and accesses the owner mutex.
mupnp_socket_close(clientSock);
mupnp_socket_delete(clientSock);
mupnp_http_server_clientdata_delete(clientData);
mupnp_thread_setuserdata(thread, NULL);
mupnp_http_server_lock(httpServer);
mupnp_thread_remove(thread);
mupnp_http_server_unlock(httpServer);
mupnp_log_debug_l4("Leaving...\n");
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
http-stop -> thread-stop -> thread-free -> http-cleanup
http stop — src/mupnp/http/http_server.c:320-330
Server stop destroys the worker list following asynchronous stop.
if (httpServer->acceptThread != NULL) {
mupnp_thread_stop(httpServer->acceptThread);
mupnp_thread_delete(httpServer->acceptThread);
httpServer->acceptThread = NULL;
}
if (httpServer->clientThreads != NULL) {
mupnp_threadlist_stop(httpServer->clientThreads);
mupnp_threadlist_delete(httpServer->clientThreads);
httpServer->clientThreads = NULL;
}
thread stop — src/mupnp/util/thread.c:451-463
Signal zero and a sleep do not establish worker exit.
#elif defined(TENGINE) && defined(PROCESS_BASE)
b_ter_tsk(thread->taskID);
#else
mupnp_log_debug_s("Killing thread %p\n", thread);
pthread_kill(thread->pThread, 0);
mupnp_sleep(MUPNP_THREAD_MIN_SLEEP);
#endif
thread free — src/mupnp/util/thread.c:262-268
Thread storage is freed without waiting for completion.
if (thread->runnableFlag == true)
mupnp_thread_stop(thread);
mupnp_thread_remove(thread);
free(thread);
http cleanup — src/mupnp/http/http_server.c:218-230
A returning worker removes/frees its thread and accesses the owner mutex.
mupnp_socket_close(clientSock);
mupnp_socket_delete(clientSock);
mupnp_http_server_clientdata_delete(clientData);
mupnp_thread_setuserdata(thread, NULL);
mupnp_http_server_lock(httpServer);
mupnp_thread_remove(thread);
mupnp_http_server_unlock(httpServer);
mupnp_log_debug_l4("Leaving...\n");
Reachability
Memory corruption requires a legitimate shutdown/restart while a peer keeps a worker active.
Severity
Medium — Memory corruption requires a legitimate shutdown/restart while a peer keeps a worker active.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: Memory corruption requires a legitimate shutdown/restart while a peer keeps a worker active.
Likelihood assessment:
- Level: medium
- Why: Memory corruption requires a legitimate shutdown/restart while a peer keeps a worker active.
Remediation
Unblock sockets and synchronize worker completion before releasing worker or owner state.
Tests:
- Unblock sockets and synchronize worker completion before releasing worker or owner state. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[5] Libxml2 parsing uses a byte length larger than its input copy
| Field | Value |
| Severity | medium |
| Confidence | high |
| Confidence rationale | Parent verified actual source flow and counterevidence; dynamic exploitation was not tested. |
| Category | memory-safety |
| CWE | CWE-125, CWE-787 |
| Affected lines | src/mupnp/soap/soap_request.c:180-188, src/mupnp/soap/soap_request.c:202-207, src/mupnp/xml/xml_parser_libxml2.c:288-295, src/mupnp/xml/xml_parser_libxml2.c:355-358, src/mupnp/xml/xml_parser_libxml2.c:298-308, src/mupnp/xml/xml_parser_libxml2.c:182-191 |
Summary
The optional libxml2 backend copies HTTP XML with mupnp_strdup, which stops at NUL, then passes the original explicit body length to the parser and recovery loop. Embedded NUL bytes make the copied allocation shorter than the consumed range, permitting out-of-bounds reads and, on invalid-character recovery, writes.
Root Cause
The optional libxml2 backend copies HTTP XML with mupnp_strdup, which stops at NUL, then passes the original explicit body length to the parser and recovery loop. Embedded NUL bytes make the copied allocation shorter than the consumed range, permitting out-of-bounds reads and, on invalid-character recovery, writes.
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
xml copy — src/mupnp/xml/xml_parser_libxml2.c:288-295
The NUL-terminated duplicate is passed with the original explicit byte length.
char* data = mupnp_strdup(parseData);
if (!data)
return false;
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
retval = mupnp_libxml2_parsewrapper(&mupnpLibxml2Handler, &libxml2Data, data, len, LIBXML2_NOFLAGS);
xml memory — src/mupnp/xml/xml_parser_libxml2.c:355-358
The libxml2 context consumes that length from the shorter allocation.
if (sax == NULL)
return -1;
ctxt = xmlCreateMemoryParserCtxt(buffer, (int)size);
xml recover — src/mupnp/xml/xml_parser_libxml2.c:298-308
Invalid-character recovery traverses the duplicate using the same length.
case XML_ERR_INVALID_CHAR:
mupnp_log_debug_s("Trying to recover from error %d.\n", retval);
if (libxml2Data.rootNode != NULL)
mupnp_xml_node_delete(libxml2Data.rootNode);
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
mupnp_xml_force_utf8(data, len);
xml recover loop — src/mupnp/xml/xml_parser_libxml2.c:182-191
The recovery loop indexes through the supplied length, beyond a NUL-shortened allocation.
while (read <= len) {
if (UTF_RANGE1_1_R == (*(data + read) & UTF_RANGE1_1)) {
read++;
continue;
}
else if (UTF_RANGE2_1_R == (*(data + read) & UTF_RANGE2_1)) {
Validation
The optional libxml2 backend copies HTTP XML with mupnp_strdup, which stops at NUL, then passes the original explicit body length to the parser and recovery loop. Embedded NUL bytes make the copied allocation shorter than the consumed range, permitting out-of-bounds reads and, on invalid-character recovery, writes.
Validation method: static source trace
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
xml copy — src/mupnp/xml/xml_parser_libxml2.c:288-295
The NUL-terminated duplicate is passed with the original explicit byte length.
char* data = mupnp_strdup(parseData);
if (!data)
return false;
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
retval = mupnp_libxml2_parsewrapper(&mupnpLibxml2Handler, &libxml2Data, data, len, LIBXML2_NOFLAGS);
xml memory — src/mupnp/xml/xml_parser_libxml2.c:355-358
The libxml2 context consumes that length from the shorter allocation.
if (sax == NULL)
return -1;
ctxt = xmlCreateMemoryParserCtxt(buffer, (int)size);
xml recover — src/mupnp/xml/xml_parser_libxml2.c:298-308
Invalid-character recovery traverses the duplicate using the same length.
case XML_ERR_INVALID_CHAR:
mupnp_log_debug_s("Trying to recover from error %d.\n", retval);
if (libxml2Data.rootNode != NULL)
mupnp_xml_node_delete(libxml2Data.rootNode);
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
mupnp_xml_force_utf8(data, len);
xml recover loop — src/mupnp/xml/xml_parser_libxml2.c:182-191
The recovery loop indexes through the supplied length, beyond a NUL-shortened allocation.
while (read <= len) {
if (UTF_RANGE1_1_R == (*(data + read) & UTF_RANGE1_1)) {
read++;
continue;
}
else if (UTF_RANGE2_1_R == (*(data + read) & UTF_RANGE2_1)) {
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
soap-input -> soap-parse -> xml-copy -> xml-memory -> xml-recover -> xml-recover-loop
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
xml copy — src/mupnp/xml/xml_parser_libxml2.c:288-295
The NUL-terminated duplicate is passed with the original explicit byte length.
char* data = mupnp_strdup(parseData);
if (!data)
return false;
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
retval = mupnp_libxml2_parsewrapper(&mupnpLibxml2Handler, &libxml2Data, data, len, LIBXML2_NOFLAGS);
xml memory — src/mupnp/xml/xml_parser_libxml2.c:355-358
The libxml2 context consumes that length from the shorter allocation.
if (sax == NULL)
return -1;
ctxt = xmlCreateMemoryParserCtxt(buffer, (int)size);
xml recover — src/mupnp/xml/xml_parser_libxml2.c:298-308
Invalid-character recovery traverses the duplicate using the same length.
case XML_ERR_INVALID_CHAR:
mupnp_log_debug_s("Trying to recover from error %d.\n", retval);
if (libxml2Data.rootNode != NULL)
mupnp_xml_node_delete(libxml2Data.rootNode);
libxml2Data.rootNode = NULL;
libxml2Data.currNode = NULL;
mupnp_xml_force_utf8(data, len);
xml recover loop — src/mupnp/xml/xml_parser_libxml2.c:182-191
The recovery loop indexes through the supplied length, beyond a NUL-shortened allocation.
while (read <= len) {
if (UTF_RANGE1_1_R == (*(data + read) & UTF_RANGE1_1)) {
read++;
continue;
}
else if (UTF_RANGE2_1_R == (*(data + read) & UTF_RANGE2_1)) {
Reachability
Requires the libxml2 backend (explicit MUPNP_XMLPARSER_LIBXML2 or iPhone guard); Expat does not use this copy. Normal NUL-free HTTP XML has consistent length. No memory disclosure channel or code execution was established.
Severity
Medium — Requires the libxml2 backend (explicit MUPNP_XMLPARSER_LIBXML2 or iPhone guard); Expat does not use this copy. Normal NUL-free HTTP XML has consistent length. No memory disclosure channel or code execution was established.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: The optional libxml2 backend copies HTTP XML with
mupnp_strdup, which stops at NUL, then passes the original explicit body length to the parser and recovery loop. Embedded NUL bytes make the copied allocation shorter than the consumed range, permitting out-of-bounds reads and, on invalid-character recovery, writes.
Likelihood assessment:
- Level: medium
- Why: Requires the libxml2 backend (explicit MUPNP_XMLPARSER_LIBXML2 or iPhone guard); Expat does not use this copy. Normal NUL-free HTTP XML has consistent length. No memory disclosure channel or code execution was established.
Remediation
Copy exactly the explicit input length into a checked allocation, or reject embedded NUL before duplication; keep parser and UTF-8 recovery strictly within the owned buffer.
Tests:
- Verify embedded NUL and invalid-character inputs are rejected without reading or writing outside the allocation, using memory instrumentation.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[6] Native HTTPS accepts unverified peer certificates
| Field | Value |
| Severity | medium |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | tls |
| CWE | CWE-295 |
| Affected lines | src/mupnp/http/http_request.c:352-356, src/mupnp/net/socket.c:668-680 |
Summary
Native HTTPS creates fresh SSL_CTX and handshakes without peer verification, trust roots or hostname validation. On-path attacker can modify HTTPS content. Requires MUPNP_USE_OPENSSL and public native HTTPS API; Curl is separate and defaults do not enable this backend.
Root Cause
Native HTTPS creates fresh SSL_CTX and handshakes without peer verification, trust roots or hostname validation. On-path attacker can modify HTTPS content. Requires MUPNP_USE_OPENSSL and public native HTTPS API; Curl is separate and defaults do not enable this backend.
https entry — src/mupnp/http/http_request.c:352-356
The public native HTTPS call selects secure socket transport.
#if defined(MUPNP_USE_OPENSSL)
mUpnpHttpResponse* mupnp_https_request_post(mUpnpHttpRequest* httpReq, const char* ipaddr, int port)
{
return mupnp_http_request_post_main(httpReq, ipaddr, port, true);
}
tls context — src/mupnp/net/socket.c:668-680
A fresh TLS context handshakes without configured peer or host verification.
#if defined(MUPNP_USE_OPENSSL)
if (mupnp_socket_isssl(sock) == true) {
sock->ctx = SSL_CTX_new(SSLv23_client_method());
sock->ssl = SSL_new(sock->ctx);
if (SSL_set_fd(sock->ssl, mupnp_socket_getid(sock)) == 0) {
mupnp_socket_close(sock);
return false;
}
if (SSL_connect(sock->ssl) < 1) {
mupnp_socket_close(sock);
return false;
}
}
Validation
Parent source review confirms the input-to-control-to-outcome path. Native HTTPS creates fresh SSL_CTX and handshakes without peer verification, trust roots or hostname validation. On-path attacker can modify HTTPS content. Requires MUPNP_USE_OPENSSL and public native HTTPS API; Curl is separate and defaults do not enable this backend.
Validation method: static source trace
https entry — src/mupnp/http/http_request.c:352-356
The public native HTTPS call selects secure socket transport.
#if defined(MUPNP_USE_OPENSSL)
mUpnpHttpResponse* mupnp_https_request_post(mUpnpHttpRequest* httpReq, const char* ipaddr, int port)
{
return mupnp_http_request_post_main(httpReq, ipaddr, port, true);
}
tls context — src/mupnp/net/socket.c:668-680
A fresh TLS context handshakes without configured peer or host verification.
#if defined(MUPNP_USE_OPENSSL)
if (mupnp_socket_isssl(sock) == true) {
sock->ctx = SSL_CTX_new(SSLv23_client_method());
sock->ssl = SSL_new(sock->ctx);
if (SSL_set_fd(sock->ssl, mupnp_socket_getid(sock)) == 0) {
mupnp_socket_close(sock);
return false;
}
if (SSL_connect(sock->ssl) < 1) {
mupnp_socket_close(sock);
return false;
}
}
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
https-entry -> tls-context
https entry — src/mupnp/http/http_request.c:352-356
The public native HTTPS call selects secure socket transport.
#if defined(MUPNP_USE_OPENSSL)
mUpnpHttpResponse* mupnp_https_request_post(mUpnpHttpRequest* httpReq, const char* ipaddr, int port)
{
return mupnp_http_request_post_main(httpReq, ipaddr, port, true);
}
tls context — src/mupnp/net/socket.c:668-680
A fresh TLS context handshakes without configured peer or host verification.
#if defined(MUPNP_USE_OPENSSL)
if (mupnp_socket_isssl(sock) == true) {
sock->ctx = SSL_CTX_new(SSLv23_client_method());
sock->ssl = SSL_new(sock->ctx);
if (SSL_set_fd(sock->ssl, mupnp_socket_getid(sock)) == 0) {
mupnp_socket_close(sock);
return false;
}
if (SSL_connect(sock->ssl) < 1) {
mupnp_socket_close(sock);
return false;
}
}
Reachability
Requires the optional native OpenSSL HTTPS API and an on-path attacker.
Severity
Medium — Requires the optional native OpenSSL HTTPS API and an on-path attacker.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: Requires the optional native OpenSSL HTTPS API and an on-path attacker.
Likelihood assessment:
- Level: medium
- Why: Requires the optional native OpenSSL HTTPS API and an on-path attacker.
Remediation
Load trust roots, require peer chain verification and hostname verification, and fail closed.
Tests:
- Load trust roots, require peer chain verification and hostname verification, and fail closed. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[7] Action wrappers leave a dangling Objective-C callback target
Summary
Constructing a CGUpnpAction installs raw self as C userdata and replaces the listener. Its dealloc leaves both installed. If the wrapper is released while a hosted C action survives, a later SOAP action dispatch messages the freed Objective-C target.
Root Cause
Constructing a CGUpnpAction installs raw self as C userdata and replaces the listener. Its dealloc leaves both installed. If the wrapper is released while a hosted C action survives, a later SOAP action dispatch messages the freed Objective-C target.
objc register — wrapper/objc/mUPnP/CGUpnpAction.m:36-44
Constructing an action wrapper installs raw self and a C callback.
-c
- (id)initWithCObject:(mUpnpAction*)cobj
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
mupnp_action_setuserdata(cObject, self);
mupnp_action_setlistener(cObject, cg_upnp_action_listener);
return self;
}
objc dealloc — wrapper/objc/mUPnP/CGUpnpAction.m:53-56
Wrapper destruction leaves installed C userdata and listener intact.
-c
- (void)dealloc
{
[super dealloc];
}
action callback — src/mupnp/control/action_ctrl.c:45-68
Incoming hosted action invokes the installed listener.
bool mupnp_action_performlistner(
mUpnpAction* action, mUpnpActionRequest* actionReq)
{
MUPNP_ACTION_LISTNER listener;
mUpnpActionResponse* actionRes;
mUpnpHttpRequest* actionReqHttpReq;
mUpnpHttpResponse* actionResHttpRes;
mupnp_log_debug_l4("Entering...\n");
listener = mupnp_action_getlistner(action);
if (listener == NULL)
return false;
actionRes = mupnp_control_action_response_new();
mupnp_action_setstatuscode(action, MUPNP_STATUS_INVALID_ACTION);
mupnp_action_setstatusdescription(action, mupnp_status_code2string(MUPNP_STATUS_INVALID_ACTION));
mupnp_action_clearoutputargumentvalues(action);
if (listener(action) == true)
mupnp_control_action_response_setresponse(actionRes, action);
else
mupnp_control_soap_response_setfaultresponse(mupnp_control_action_response_getsoapresponse(actionRes), mupnp_action_getstatuscode(action), mupnp_action_getstatusdescription(action));
void mUpnpAction
Definition CGUpnpAction.h:15
objc callback — wrapper/objc/mUPnP/CGUpnpAction.m:17-29
Later callback dispatch messages the stored wrapper pointer.
-c
{
if (!cAction)
return FALSE;
CGUpnpAction* objcAction = (CGUpnpAction*)mupnp_action_getuserdata(cAction);
if (!objcAction)
return FALSE;
SEL actionReceived = @selector(actionReceived);
if (!actionReceived)
return FALSE;
if (![objcAction respondsToSelector:actionReceived])
return FALSE;
[objcAction performSelector:actionReceived];
return TRUE;
Validation
Constructing a CGUpnpAction installs raw self as C userdata and replaces the listener. Its dealloc leaves both installed. If the wrapper is released while a hosted C action survives, a later SOAP action dispatch messages the freed Objective-C target.
Validation method: static source trace
objc register — wrapper/objc/mUPnP/CGUpnpAction.m:36-44
Constructing an action wrapper installs raw self and a C callback.
-c
- (id)initWithCObject:(mUpnpAction*)cobj
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
mupnp_action_setuserdata(cObject, self);
mupnp_action_setlistener(cObject, cg_upnp_action_listener);
return self;
}
objc dealloc — wrapper/objc/mUPnP/CGUpnpAction.m:53-56
Wrapper destruction leaves installed C userdata and listener intact.
-c
- (void)dealloc
{
[super dealloc];
}
action callback — src/mupnp/control/action_ctrl.c:45-68
Incoming hosted action invokes the installed listener.
bool mupnp_action_performlistner(
mUpnpAction* action, mUpnpActionRequest* actionReq)
{
MUPNP_ACTION_LISTNER listener;
mUpnpActionResponse* actionRes;
mUpnpHttpRequest* actionReqHttpReq;
mUpnpHttpResponse* actionResHttpRes;
mupnp_log_debug_l4("Entering...\n");
listener = mupnp_action_getlistner(action);
if (listener == NULL)
return false;
actionRes = mupnp_control_action_response_new();
mupnp_action_setstatuscode(action, MUPNP_STATUS_INVALID_ACTION);
mupnp_action_setstatusdescription(action, mupnp_status_code2string(MUPNP_STATUS_INVALID_ACTION));
mupnp_action_clearoutputargumentvalues(action);
if (listener(action) == true)
mupnp_control_action_response_setresponse(actionRes, action);
else
mupnp_control_soap_response_setfaultresponse(mupnp_control_action_response_getsoapresponse(actionRes), mupnp_action_getstatuscode(action), mupnp_action_getstatusdescription(action));
objc callback — wrapper/objc/mUPnP/CGUpnpAction.m:17-29
Later callback dispatch messages the stored wrapper pointer.
-c
{
if (!cAction)
return FALSE;
CGUpnpAction* objcAction = (CGUpnpAction*)mupnp_action_getuserdata(cAction);
if (!objcAction)
return FALSE;
SEL actionReceived = @selector(actionReceived);
if (!actionReceived)
return FALSE;
if (![objcAction respondsToSelector:actionReceived])
return FALSE;
[objcAction performSelector:actionReceived];
return TRUE;
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
objc-register -> objc-dealloc -> action-callback -> objc-callback
objc register — wrapper/objc/mUPnP/CGUpnpAction.m:36-44
Constructing an action wrapper installs raw self and a C callback.
-c
- (id)initWithCObject:(mUpnpAction*)cobj
{
if ((self = [super init]) == nil)
return nil;
cObject = cobj;
mupnp_action_setuserdata(cObject, self);
mupnp_action_setlistener(cObject, cg_upnp_action_listener);
return self;
}
objc dealloc — wrapper/objc/mUPnP/CGUpnpAction.m:53-56
Wrapper destruction leaves installed C userdata and listener intact.
-c
- (void)dealloc
{
[super dealloc];
}
action callback — src/mupnp/control/action_ctrl.c:45-68
Incoming hosted action invokes the installed listener.
bool mupnp_action_performlistner(
mUpnpAction* action, mUpnpActionRequest* actionReq)
{
MUPNP_ACTION_LISTNER listener;
mUpnpActionResponse* actionRes;
mUpnpHttpRequest* actionReqHttpReq;
mUpnpHttpResponse* actionResHttpRes;
mupnp_log_debug_l4("Entering...\n");
listener = mupnp_action_getlistner(action);
if (listener == NULL)
return false;
actionRes = mupnp_control_action_response_new();
mupnp_action_setstatuscode(action, MUPNP_STATUS_INVALID_ACTION);
mupnp_action_setstatusdescription(action, mupnp_status_code2string(MUPNP_STATUS_INVALID_ACTION));
mupnp_action_clearoutputargumentvalues(action);
if (listener(action) == true)
mupnp_control_action_response_setresponse(actionRes, action);
else
mupnp_control_soap_response_setfaultresponse(mupnp_control_action_response_getsoapresponse(actionRes), mupnp_action_getstatuscode(action), mupnp_action_getstatusdescription(action));
objc callback — wrapper/objc/mUPnP/CGUpnpAction.m:17-29
Later callback dispatch messages the stored wrapper pointer.
-c
{
if (!cAction)
return FALSE;
CGUpnpAction* objcAction = (CGUpnpAction*)mupnp_action_getuserdata(cAction);
if (!objcAction)
return FALSE;
SEL actionReceived = @selector(actionReceived);
if (!actionReceived)
return FALSE;
if (![objcAction respondsToSelector:actionReceived])
return FALSE;
[objcAction performSelector:actionReceived];
return TRUE;
Reachability
Requires an embedding application to wrap a hosted action, release the wrapper and keep the native action/listener alive. Retaining the wrapper avoids the immediate dangling condition. The callback accesses the object before checking whether the selector is supported.
Severity
Medium — Requires an embedding application to wrap a hosted action, release the wrapper and keep the native action/listener alive. Retaining the wrapper avoids the immediate dangling condition. The callback accesses the object before checking whether the selector is supported.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: high
- Why: Constructing a
CGUpnpAction installs raw self as C userdata and replaces the listener. Its dealloc leaves both installed. If the wrapper is released while a hosted C action survives, a later SOAP action dispatch messages the freed Objective-C target.
Likelihood assessment:
- Level: medium
- Why: Requires an embedding application to wrap a hosted action, release the wrapper and keep the native action/listener alive. Retaining the wrapper avoids the immediate dangling condition. The callback accesses the object before checking whether the selector is supported.
Remediation
Separate inspection wrappers from registration; retain callback targets for the registration lifetime or unregister userdata/listener during synchronized teardown.
Tests:
- Destroy an action wrapper while its hosted C action remains alive and verify callback dispatch never touches the released object.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[8] Deep XML input can exhaust the stack during tree cleanup
| Field | Value |
| Severity | medium |
| Confidence | high |
| Confidence rationale | Parent verified actual source flow and counterevidence; dynamic exploitation was not tested. |
| Category | denial-of-service |
| CWE | CWE-674 |
| Affected lines | src/mupnp/soap/soap_request.c:180-188, src/mupnp/soap/soap_request.c:202-207, src/mupnp/xml/xml_parser_expat.c:95-112, src/mupnp/xml/xml_parser_expat.c:194-199, src/mupnp/xml/xml_node.c:48-59 |
Summary
The Expat callbacks build an arbitrarily deep child tree without a depth budget. XML parse failure deletes that tree through recursive child-list destruction. Network SOAP input reaches parsing before service/action validation, so a deeply nested malformed document can exhaust the native stack.
Root Cause
The Expat callbacks build an arbitrarily deep child tree without a depth budget. XML parse failure deletes that tree through recursive child-list destruction. Network SOAP input reaches parsing before service/action validation, so a deeply nested malformed document can exhaust the native stack.
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
expat depth — src/mupnp/xml/xml_parser_expat.c:95-112
Each nested element allocates and links a child without depth accounting.
expatData = (mUpnpExpatData*)userData;
node = mupnp_xml_node_new();
mupnp_xml_node_setname(node, (char*)el);
for (n = 0; attr[n]; n += 2)
mupnp_xml_node_setattribute(node, (char*)attr[n], (char*)attr[n + 1]);
if (expatData->rootNode != NULL) {
if (expatData->currNode != NULL)
mupnp_xml_node_addchildnode(expatData->currNode, node);
else
mupnp_xml_node_addchildnode(expatData->rootNode, node);
}
else
expatData->rootNode = node;
expatData->currNode = node;
expat error — src/mupnp/xml/xml_parser_expat.c:194-199
Parse failure deletes the partially constructed tree.
parser->parseResult = XML_Parse(p, data, len, 1);
XML_ParserFree(p);
if (parser->parseResult == 0 ) {
if (expatData.rootNode != NULL)
mupnp_xml_node_delete(expatData.rootNode);
xml delete — src/mupnp/xml/xml_node.c:48-59
Node deletion recursively deletes child lists.
void mupnp_xml_node_delete(mUpnpXmlNode* node)
{
mupnp_log_debug_l4("Entering...\n");
mupnp_list_remove((mUpnpList*)node);
mupnp_string_delete(node->name);
mupnp_string_delete(node->value);
mupnp_xml_attributelist_delete(node->attrList);
mupnp_xml_nodelist_delete(node->nodeList);
if (node->userDataDestructorFunc != NULL)
node->userDataDestructorFunc(node->userData);
free(node);
Validation
The Expat callbacks build an arbitrarily deep child tree without a depth budget. XML parse failure deletes that tree through recursive child-list destruction. Network SOAP input reaches parsing before service/action validation, so a deeply nested malformed document can exhaust the native stack.
Validation method: static source trace
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
expat depth — src/mupnp/xml/xml_parser_expat.c:95-112
Each nested element allocates and links a child without depth accounting.
expatData = (mUpnpExpatData*)userData;
node = mupnp_xml_node_new();
mupnp_xml_node_setname(node, (char*)el);
for (n = 0; attr[n]; n += 2)
mupnp_xml_node_setattribute(node, (char*)attr[n], (char*)attr[n + 1]);
if (expatData->rootNode != NULL) {
if (expatData->currNode != NULL)
mupnp_xml_node_addchildnode(expatData->currNode, node);
else
mupnp_xml_node_addchildnode(expatData->rootNode, node);
}
else
expatData->rootNode = node;
expatData->currNode = node;
expat error — src/mupnp/xml/xml_parser_expat.c:194-199
Parse failure deletes the partially constructed tree.
parser->parseResult = XML_Parse(p, data, len, 1);
XML_ParserFree(p);
if (parser->parseResult == 0 ) {
if (expatData.rootNode != NULL)
mupnp_xml_node_delete(expatData.rootNode);
xml delete — src/mupnp/xml/xml_node.c:48-59
Node deletion recursively deletes child lists.
void mupnp_xml_node_delete(mUpnpXmlNode* node)
{
mupnp_log_debug_l4("Entering...\n");
mupnp_list_remove((mUpnpList*)node);
mupnp_string_delete(node->name);
mupnp_string_delete(node->value);
mupnp_xml_attributelist_delete(node->attrList);
mupnp_xml_nodelist_delete(node->nodeList);
if (node->userDataDestructorFunc != NULL)
node->userDataDestructorFunc(node->userData);
free(node);
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
soap-input -> soap-parse -> expat-depth -> expat-error -> xml-delete
soap input — src/mupnp/soap/soap_request.c:180-188
HTTP content and explicit framing length pass together into SOAP parsing.
content = mupnp_http_request_getcontent(httpReq);
contentLen = mupnp_http_request_getcontentlength(httpReq);
if (content == NULL || contentLen <= 0)
return false;
mupnp_log_debug_l4("Leaving...\n");
return mupnp_soap_request_parsemessage(soapReq, content, contentLen);
soap parse — src/mupnp/soap/soap_request.c:202-207
SOAP forwards bytes and length to the active XML backend.
if (msgLen <= 0)
return false;
xmlParser = mupnp_xml_parser_new();
parseRet = mupnp_xml_parse(xmlParser, soapReq->rootNodeList, msg, msgLen);
mupnp_xml_parser_delete(xmlParser);
expat depth — src/mupnp/xml/xml_parser_expat.c:95-112
Each nested element allocates and links a child without depth accounting.
expatData = (mUpnpExpatData*)userData;
node = mupnp_xml_node_new();
mupnp_xml_node_setname(node, (char*)el);
for (n = 0; attr[n]; n += 2)
mupnp_xml_node_setattribute(node, (char*)attr[n], (char*)attr[n + 1]);
if (expatData->rootNode != NULL) {
if (expatData->currNode != NULL)
mupnp_xml_node_addchildnode(expatData->currNode, node);
else
mupnp_xml_node_addchildnode(expatData->rootNode, node);
}
else
expatData->rootNode = node;
expatData->currNode = node;
expat error — src/mupnp/xml/xml_parser_expat.c:194-199
Parse failure deletes the partially constructed tree.
parser->parseResult = XML_Parse(p, data, len, 1);
XML_ParserFree(p);
if (parser->parseResult == 0 ) {
if (expatData.rootNode != NULL)
mupnp_xml_node_delete(expatData.rootNode);
xml delete — src/mupnp/xml/xml_node.c:48-59
Node deletion recursively deletes child lists.
void mupnp_xml_node_delete(mUpnpXmlNode* node)
{
mupnp_log_debug_l4("Entering...\n");
mupnp_list_remove((mUpnpList*)node);
mupnp_string_delete(node->name);
mupnp_string_delete(node->value);
mupnp_xml_attributelist_delete(node->attrList);
mupnp_xml_nodelist_delete(node->nodeList);
if (node->userDataDestructorFunc != NULL)
node->userDataDestructorFunc(node->userData);
free(node);
Reachability
Applies to the Expat backend; libxml2 has separate parser limits. The exact exhaustion threshold depends on native stack size and dependency behavior. No runtime reproduction was performed.
Severity
Medium — Applies to the Expat backend; libxml2 has separate parser limits. The exact exhaustion threshold depends on native stack size and dependency behavior. No runtime reproduction was performed.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: medium
- Why: The Expat callbacks build an arbitrarily deep child tree without a depth budget. XML parse failure deletes that tree through recursive child-list destruction. Network SOAP input reaches parsing before service/action validation, so a deeply nested malformed document can exhaust the native stack.
Likelihood assessment:
- Level: high
- Why: Applies to the Expat backend; libxml2 has separate parser limits. The exact exhaustion threshold depends on native stack size and dependency behavior. No runtime reproduction was performed.
Remediation
Enforce a conservative element-depth and node budget during parsing, abort safely on excess, and make tree destruction iterative.
Tests:
- Verify excessive nesting fails safely on both malformed and valid documents and ordinary SOAP still parses.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[9] Unbounded M-SEARCH MX blocks the SSDP worker
| Field | Value |
| Severity | low |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | denial-of-service |
| CWE | CWE-400 |
| Affected lines | src/mupnp/device_ssdp_server.c:84-107, src/mupnp/util/time.c:64-74 |
Summary
Digit validation accepts excessive MX. Delay runs synchronously on sole SSDP worker; duplicate filtering does not cap first delay. Windows and Unix accepting long usleep durations can stop discovery processing for extended periods; platform applicability varies.
Root Cause
Digit validation accepts excessive MX. Delay runs synchronously on sole SSDP worker; duplicate filtering does not cap first delay. Windows and Unix accepting long usleep durations can stop discovery processing for extended periods; platform applicability varies.
mx sleep — src/mupnp/device_ssdp_server.c:84-107
Digits are accepted without a range cap and MX then controls a synchronous delay.
if (ssdpMXString == NULL || mupnp_strlen(ssdpMXString) == 0)
return;
for (n = 0; n < strlen(ssdpMXString); n++) {
if (isdigit(ssdpMXString[n]) == 0)
return;
}
if (mupnp_strlen(ssdpST) <= 0)
return;
if (filter_duplicate_m_search(ssdpPkt))
return;
ssdpMX = mupnp_ssdp_packet_getmx(ssdpPkt);
mupnp_log_debug("Sleeping for a while... (MX:%d)\n", ssdpMX);
mupnp_waitrandom((ssdpMX * 1000) / 4);
wait random — src/mupnp/util/time.c:64-74
The random delay reaches the platform sleep routine.
void mupnp_waitrandom(mUpnpTime mtime)
{
double factor;
long waitTime;
mupnp_log_debug_l4("Entering...\n");
factor = (double)rand() / (double)RAND_MAX;
waitTime = (long)((double)mtime * factor);
mupnp_wait(waitTime);
Validation
Parent source review confirms the input-to-control-to-outcome path. Digit validation accepts excessive MX. Delay runs synchronously on sole SSDP worker; duplicate filtering does not cap first delay. Windows and Unix accepting long usleep durations can stop discovery processing for extended periods; platform applicability varies.
Validation method: static source trace
mx sleep — src/mupnp/device_ssdp_server.c:84-107
Digits are accepted without a range cap and MX then controls a synchronous delay.
if (ssdpMXString == NULL || mupnp_strlen(ssdpMXString) == 0)
return;
for (n = 0; n < strlen(ssdpMXString); n++) {
if (isdigit(ssdpMXString[n]) == 0)
return;
}
if (mupnp_strlen(ssdpST) <= 0)
return;
if (filter_duplicate_m_search(ssdpPkt))
return;
ssdpMX = mupnp_ssdp_packet_getmx(ssdpPkt);
mupnp_log_debug("Sleeping for a while... (MX:%d)\n", ssdpMX);
mupnp_waitrandom((ssdpMX * 1000) / 4);
wait random — src/mupnp/util/time.c:64-74
The random delay reaches the platform sleep routine.
void mupnp_waitrandom(mUpnpTime mtime)
{
double factor;
long waitTime;
mupnp_log_debug_l4("Entering...\n");
factor = (double)rand() / (double)RAND_MAX;
waitTime = (long)((double)mtime * factor);
mupnp_wait(waitTime);
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
mx-sleep -> wait-random
mx sleep — src/mupnp/device_ssdp_server.c:84-107
Digits are accepted without a range cap and MX then controls a synchronous delay.
if (ssdpMXString == NULL || mupnp_strlen(ssdpMXString) == 0)
return;
for (n = 0; n < strlen(ssdpMXString); n++) {
if (isdigit(ssdpMXString[n]) == 0)
return;
}
if (mupnp_strlen(ssdpST) <= 0)
return;
if (filter_duplicate_m_search(ssdpPkt))
return;
ssdpMX = mupnp_ssdp_packet_getmx(ssdpPkt);
mupnp_log_debug("Sleeping for a while... (MX:%d)\n", ssdpMX);
mupnp_waitrandom((ssdpMX * 1000) / 4);
wait random — src/mupnp/util/time.c:64-74
The random delay reaches the platform sleep routine.
void mupnp_waitrandom(mUpnpTime mtime)
{
double factor;
long waitTime;
mupnp_log_debug_l4("Entering...\n");
factor = (double)rand() / (double)RAND_MAX;
waitTime = (long)((double)mtime * factor);
mupnp_wait(waitTime);
Reachability
The discovery subsystem stalls, with platform-dependent long-sleep behavior.
Severity
Low — The discovery subsystem stalls, with platform-dependent long-sleep behavior.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: low
- Why: The discovery subsystem stalls, with platform-dependent long-sleep behavior.
Likelihood assessment:
- Level: high
- Why: The discovery subsystem stalls, with platform-dependent long-sleep behavior.
Remediation
Range-check and cap MX and schedule responses without blocking reception.
Tests:
- Range-check and cap MX and schedule responses without blocking reception. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[10] Test presentation handler sends uninitialized stack contents
| Field | Value |
| Severity | low |
| Confidence | high |
| Confidence rationale | Parent verified actual source flow and counterevidence; dynamic exploitation was not tested. |
| Category | information-disclosure |
| CWE | CWE-200 |
| Affected lines | test/TestDevice.c:309-313, test/DeviceTest.cpp:41-44, test/TestDevice.c:208-224, test/TestDevice.c:273-278 |
Summary
The network test device's presentation handler declares a stack array whose only initialization is commented out, then copies and measures it as a C string before sending it. A reachable peer during test execution can receive residual stack bytes or cause an out-of-bounds read.
Root Cause
The network test device's presentation handler declares a stack array whose only initialization is commented out, then copies and measures it as a C string before sending it. A reachable peer during test execution can receive residual stack bytes or cause an out-of-bounds read.
test register — test/TestDevice.c:309-313
The test device registers this HTTP listener.
mupnp_device_setactionlistener(testDev, upnp_test_actionreceived);
mupnp_device_setquerylistener(testDev, upnp_test_queryreceived);
mupnp_device_sethttplistener(testDev, upnp_test_device_httprequestrecieved);
return testDev;
test start — test/DeviceTest.cpp:41-44
The test starts the device, exposing the listener for the test duration.
BOOST_REQUIRE(mupnp_device_start(testDev));
BOOST_REQUIRE(mupnp_device_stop(testDev));
mupnp_device_delete(testDev);
test buffer — test/TestDevice.c:208-224
The presentation handler declares an uninitialized stack array.
void upnp_test_device_httprequestrecieved(mUpnpHttpRequest* httpReq)
{
char* uri;
char content[2048];
mUpnpHttpResponse* httpRes;
uri = mupnp_http_request_geturi(httpReq);
if (strcmp(uri, "/presentation") != 0) {
mupnp_device_httprequestrecieved(httpReq);
return;
}
test send — test/TestDevice.c:273-278
The uninitialized array is copied as a C string and sent to the requester.
httpRes = mupnp_http_response_new();
mupnp_http_response_setstatuscode(httpRes, MUPNP_HTTP_STATUS_OK);
mupnp_http_response_setcontent(httpRes, content);
mupnp_http_response_setcontenttype(httpRes, "text/html");
mupnp_http_response_setcontentlength(httpRes, strlen(content));
mupnp_http_request_postresponse(httpReq, httpRes);
Validation
The network test device's presentation handler declares a stack array whose only initialization is commented out, then copies and measures it as a C string before sending it. A reachable peer during test execution can receive residual stack bytes or cause an out-of-bounds read.
Validation method: static source trace
test register — test/TestDevice.c:309-313
The test device registers this HTTP listener.
mupnp_device_setactionlistener(testDev, upnp_test_actionreceived);
mupnp_device_setquerylistener(testDev, upnp_test_queryreceived);
mupnp_device_sethttplistener(testDev, upnp_test_device_httprequestrecieved);
return testDev;
test start — test/DeviceTest.cpp:41-44
The test starts the device, exposing the listener for the test duration.
BOOST_REQUIRE(mupnp_device_start(testDev));
BOOST_REQUIRE(mupnp_device_stop(testDev));
mupnp_device_delete(testDev);
test buffer — test/TestDevice.c:208-224
The presentation handler declares an uninitialized stack array.
void upnp_test_device_httprequestrecieved(mUpnpHttpRequest* httpReq)
{
char* uri;
char content[2048];
mUpnpHttpResponse* httpRes;
uri = mupnp_http_request_geturi(httpReq);
if (strcmp(uri, "/presentation") != 0) {
mupnp_device_httprequestrecieved(httpReq);
return;
}
test send — test/TestDevice.c:273-278
The uninitialized array is copied as a C string and sent to the requester.
httpRes = mupnp_http_response_new();
mupnp_http_response_setstatuscode(httpRes, MUPNP_HTTP_STATUS_OK);
mupnp_http_response_setcontent(httpRes, content);
mupnp_http_response_setcontenttype(httpRes, "text/html");
mupnp_http_response_setcontentlength(httpRes, strlen(content));
mupnp_http_request_postresponse(httpReq, httpRes);
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
test-register -> test-start -> test-buffer -> test-send
test register — test/TestDevice.c:309-313
The test device registers this HTTP listener.
mupnp_device_setactionlistener(testDev, upnp_test_actionreceived);
mupnp_device_setquerylistener(testDev, upnp_test_queryreceived);
mupnp_device_sethttplistener(testDev, upnp_test_device_httprequestrecieved);
return testDev;
test start — test/DeviceTest.cpp:41-44
The test starts the device, exposing the listener for the test duration.
BOOST_REQUIRE(mupnp_device_start(testDev));
BOOST_REQUIRE(mupnp_device_stop(testDev));
mupnp_device_delete(testDev);
test buffer — test/TestDevice.c:208-224
The presentation handler declares an uninitialized stack array.
void upnp_test_device_httprequestrecieved(mUpnpHttpRequest* httpReq)
{
char* uri;
char content[2048];
mUpnpHttpResponse* httpRes;
uri = mupnp_http_request_geturi(httpReq);
if (strcmp(uri, "/presentation") != 0) {
mupnp_device_httprequestrecieved(httpReq);
return;
}
test send — test/TestDevice.c:273-278
The uninitialized array is copied as a C string and sent to the requester.
httpRes = mupnp_http_response_new();
mupnp_http_response_setstatuscode(httpRes, MUPNP_HTTP_STATUS_OK);
mupnp_http_response_setcontent(httpRes, content);
mupnp_http_response_setcontenttype(httpRes, "text/html");
mupnp_http_response_setcontentlength(httpRes, strlen(content));
mupnp_http_request_postresponse(httpReq, httpRes);
Reachability
Test-only; CMake tests default OFF, CI explicitly enables them. The listener's exposure window and confidential stack contents depend on the test environment; no concrete secret was observed.
Severity
Low — Test-only; CMake tests default OFF, CI explicitly enables them. The listener's exposure window and confidential stack contents depend on the test environment; no concrete secret was observed.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: medium
- Why: The network test device's presentation handler declares a stack array whose only initialization is commented out, then copies and measures it as a C string before sending it. A reachable peer during test execution can receive residual stack bytes or cause an out-of-bounds read.
Likelihood assessment:
- Level: low
- Why: Test-only; CMake tests default OFF, CI explicitly enables them. The listener's exposure window and confidential stack contents depend on the test environment; no concrete secret was observed.
Remediation
Send an initialized literal with an explicit known length; use loopback for network test listeners when external reachability is unnecessary.
Tests:
- Verify the test presentation response is exactly the fixed initialized bytes and uses the known length.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[11] Media directory dump follows container cycles without a bound
| Field | Value |
| Severity | low |
| Confidence | high |
| Confidence rationale | Parent verified actual source flow and counterevidence; dynamic exploitation was not tested. |
| Category | denial-of-service |
| CWE | CWE-674 |
| Affected lines | examples/upnpavdump/upnpavdump.c:48-58, examples/upnpavdump/upnpavdump.c:60-70, examples/upnpavchk/upnpavchk.c:61-70, examples/upnpavdump/macosx/xcode30/upnpavdump.m:46-49 |
Summary
The media dump utilities recursively browse every container ID supplied by a successful remote DIDL response. No visited-ID set or depth/request budget stops a cyclic or excessively deep hierarchy, retaining native frames and XML trees until stack or memory exhaustion.
Root Cause
The media dump utilities recursively browse every container ID supplied by a successful remote DIDL response. No visited-ID set or depth/request budget stops a cyclic or excessively deep hierarchy, retaining native frames and XML trees until stack or memory exhaustion.
media response — examples/upnpavdump/upnpavdump.c:48-58
A successful remote action supplies the XML containing the next container IDs.
if (!mupnp_action_post(browseAction))
return;
resultXml = mupnp_action_getargumentvaluebyname(browseAction, "Result");
if (mupnp_strlen(resultXml) <= 0)
return;
rootNode = mupnp_xml_nodelist_new();
xmlParser = mupnp_xml_parser_new();
if (mupnp_xml_parse(xmlParser, rootNode, resultXml, mupnp_strlen(resultXml))) {
media recursion — examples/upnpavdump/upnpavdump.c:60-70
Peer-returned container IDs drive recursive Browse traversal without a cycle or depth guard.
if (didlNode) {
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
print_content_directory(browseAction, (indent + 1), id);
}
media chk recursion — examples/upnpavchk/upnpavchk.c:61-70
The sibling traversal has the same unbounded container recursion.
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
PrintContentDirectory(browseAction, (indent + 1), id);
}
media objc recursion — examples/upnpavdump/macosx/xcode30/upnpavdump.m:46-49
The macOS sibling also recurses using peer container IDs.
-c
if ([[contentNode name] isEqualToString:@"container"]) {
NSLog(@"%@ [%@] %@", indentStr, objId, title);
PrintContentDirectory(browseAction, (indent + 1), objId);
}
Validation
The media dump utilities recursively browse every container ID supplied by a successful remote DIDL response. No visited-ID set or depth/request budget stops a cyclic or excessively deep hierarchy, retaining native frames and XML trees until stack or memory exhaustion.
Validation method: static source trace
media response — examples/upnpavdump/upnpavdump.c:48-58
A successful remote action supplies the XML containing the next container IDs.
if (!mupnp_action_post(browseAction))
return;
resultXml = mupnp_action_getargumentvaluebyname(browseAction, "Result");
if (mupnp_strlen(resultXml) <= 0)
return;
rootNode = mupnp_xml_nodelist_new();
xmlParser = mupnp_xml_parser_new();
if (mupnp_xml_parse(xmlParser, rootNode, resultXml, mupnp_strlen(resultXml))) {
media recursion — examples/upnpavdump/upnpavdump.c:60-70
Peer-returned container IDs drive recursive Browse traversal without a cycle or depth guard.
if (didlNode) {
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
print_content_directory(browseAction, (indent + 1), id);
}
media chk recursion — examples/upnpavchk/upnpavchk.c:61-70
The sibling traversal has the same unbounded container recursion.
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
PrintContentDirectory(browseAction, (indent + 1), id);
}
media objc recursion — examples/upnpavdump/macosx/xcode30/upnpavdump.m:46-49
The macOS sibling also recurses using peer container IDs.
-c
if ([[contentNode name] isEqualToString:@"container"]) {
NSLog(@"%@ [%@] %@", indentStr, objId, title);
PrintContentDirectory(browseAction, (indent + 1), objId);
}
Limitations:
- No application code or vulnerability-triggering input was executed.
Dataflow
media-response -> media-recursion -> media-chk-recursion -> media-objc-recursion
media response — examples/upnpavdump/upnpavdump.c:48-58
A successful remote action supplies the XML containing the next container IDs.
if (!mupnp_action_post(browseAction))
return;
resultXml = mupnp_action_getargumentvaluebyname(browseAction, "Result");
if (mupnp_strlen(resultXml) <= 0)
return;
rootNode = mupnp_xml_nodelist_new();
xmlParser = mupnp_xml_parser_new();
if (mupnp_xml_parse(xmlParser, rootNode, resultXml, mupnp_strlen(resultXml))) {
media recursion — examples/upnpavdump/upnpavdump.c:60-70
Peer-returned container IDs drive recursive Browse traversal without a cycle or depth guard.
if (didlNode) {
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
print_content_directory(browseAction, (indent + 1), id);
}
media chk recursion — examples/upnpavchk/upnpavchk.c:61-70
The sibling traversal has the same unbounded container recursion.
for (cnode = mupnp_xml_node_getchildnodes(didlNode); cnode; cnode = mupnp_xml_node_next(cnode)) {
id = mupnp_xml_node_getattributevalue(cnode, "id");
title = mupnp_xml_node_getchildnodevalue(cnode, "dc:title");
if (mupnp_xml_node_isname(cnode, "container")) {
printf(" %s[%s]%s\n",
indentStr,
id,
((0 < mupnp_strlen(title)) ? title : ""));
PrintContentDirectory(browseAction, (indent + 1), id);
}
media objc recursion — examples/upnpavdump/macosx/xcode30/upnpavdump.m:46-49
The macOS sibling also recurses using peer container IDs.
-c
if ([[contentNode name] isEqualToString:@"container"]) {
NSLog(@"%@ [%@] %@", indentStr, objId, title);
PrintContentDirectory(browseAction, (indent + 1), objId);
}
Reachability
Availability impact is confined to a user-invoked dump utility. The primary C upnpavdump target is in default example builds; other sibling targets have separate/legacy build routes. Indentation is bounded, which prevents a distinct buffer overflow but does not bound recursion.
Severity
Low — Availability impact is confined to a user-invoked dump utility. The primary C upnpavdump target is in default example builds; other sibling targets have separate/legacy build routes. Indentation is bounded, which prevents a distinct buffer overflow but does not bound recursion.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: low
- Why: The media dump utilities recursively browse every container ID supplied by a successful remote DIDL response. No visited-ID set or depth/request budget stops a cyclic or excessively deep hierarchy, retaining native frames and XML trees until stack or memory exhaustion.
Likelihood assessment:
- Level: high
- Why: Availability impact is confined to a user-invoked dump utility. The primary C upnpavdump target is in default example builds; other sibling targets have separate/legacy build routes. Indentation is bounded, which prevents a distinct buffer overflow but does not bound recursion.
Remediation
Use iterative traversal with a per-device visited-ID set and explicit depth, response-count and content budgets.
Tests:
- Verify cyclic and overly deep container responses terminate within the configured budget.
Preventive controls:
- Centralize and enforce the repaired lifetime or input-limit invariant.
[12] Malformed SSDP packets leak tokenizers without bound
| Field | Value |
| Severity | low |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | resource-leak |
| CWE | CWE-401 |
| Affected lines | src/mupnp/ssdp/httpmu_socket.c:48-62, src/mupnp/ssdp/ssdp_packet.c:111-115 |
Summary
Nonempty malformed datagram produces no tokenizer token. New tokenizer is abandoned on early return before message validation. Packet cleanup does not own the local tokenizer. Sustained unauthenticated UDP traffic leaks heap.
Root Cause
Nonempty malformed datagram produces no tokenizer token. New tokenizer is abandoned on early return before message validation. Packet cleanup does not own the local tokenizer. Sustained unauthenticated UDP traffic leaks heap.
ssdp before validation — src/mupnp/ssdp/httpmu_socket.c:48-62
Headers are parsed from received datagram bytes before listener validation.
dgmPkt = mupnp_ssdp_packet_getdatagrampacket(ssdpPkt);
recvLen = mupnp_socket_recv(sock, dgmPkt);
if (recvLen <= 0)
return recvLen;
ssdpData = mupnp_socket_datagram_packet_getdata(dgmPkt);
mupnp_ssdp_packet_setheader(ssdpPkt, ssdpData);
mupnp_log_debug_l4("Leaving...\n");
ssdp leak — src/mupnp/ssdp/ssdp_packet.c:111-115
The allocated local tokenizer is abandoned when no tokens are present.
ssdpTok = mupnp_string_tokenizer_new(ssdpMsg, MUPNP_HTTP_CRLF);
if (mupnp_string_tokenizer_hasmoretoken(ssdpTok) == false)
return;
Validation
Parent source review confirms the input-to-control-to-outcome path. Nonempty malformed datagram produces no tokenizer token. New tokenizer is abandoned on early return before message validation. Packet cleanup does not own the local tokenizer. Sustained unauthenticated UDP traffic leaks heap.
Validation method: static source trace
ssdp before validation — src/mupnp/ssdp/httpmu_socket.c:48-62
Headers are parsed from received datagram bytes before listener validation.
dgmPkt = mupnp_ssdp_packet_getdatagrampacket(ssdpPkt);
recvLen = mupnp_socket_recv(sock, dgmPkt);
if (recvLen <= 0)
return recvLen;
ssdpData = mupnp_socket_datagram_packet_getdata(dgmPkt);
mupnp_ssdp_packet_setheader(ssdpPkt, ssdpData);
mupnp_log_debug_l4("Leaving...\n");
ssdp leak — src/mupnp/ssdp/ssdp_packet.c:111-115
The allocated local tokenizer is abandoned when no tokens are present.
ssdpTok = mupnp_string_tokenizer_new(ssdpMsg, MUPNP_HTTP_CRLF);
if (mupnp_string_tokenizer_hasmoretoken(ssdpTok) == false)
return;
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
ssdp-before-validation -> ssdp-leak
ssdp before validation — src/mupnp/ssdp/httpmu_socket.c:48-62
Headers are parsed from received datagram bytes before listener validation.
dgmPkt = mupnp_ssdp_packet_getdatagrampacket(ssdpPkt);
recvLen = mupnp_socket_recv(sock, dgmPkt);
if (recvLen <= 0)
return recvLen;
ssdpData = mupnp_socket_datagram_packet_getdata(dgmPkt);
mupnp_ssdp_packet_setheader(ssdpPkt, ssdpData);
mupnp_log_debug_l4("Leaving...\n");
ssdp leak — src/mupnp/ssdp/ssdp_packet.c:111-115
The allocated local tokenizer is abandoned when no tokens are present.
ssdpTok = mupnp_string_tokenizer_new(ssdpMsg, MUPNP_HTTP_CRLF);
if (mupnp_string_tokenizer_hasmoretoken(ssdpTok) == false)
return;
Reachability
Sustained traffic is required to turn a small per-packet leak into material memory pressure.
Severity
Low — Sustained traffic is required to turn a small per-packet leak into material memory pressure.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: low
- Why: Sustained traffic is required to turn a small per-packet leak into material memory pressure.
Likelihood assessment:
- Level: high
- Why: Sustained traffic is required to turn a small per-packet leak into material memory pressure.
Remediation
Destroy tokenizer on all exit paths.
Tests:
- Destroy tokenizer on all exit paths. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
[13] A zero-length UDP packet permanently stops SSDP reception
| Field | Value |
| Severity | low |
| Confidence | high |
| Confidence rationale | Parent verified the source control, callers, signed return semantics and counterevidence; no runtime reproduction. |
| Category | denial-of-service |
| CWE | CWE-754 |
| Affected lines | src/mupnp/net/socket.c:966-977, src/mupnp/ssdp/ssdp_server.c:148-153, src/mupnp/ssdp/ssdp_response_server.c:150-157 |
Summary
Empty datagram yields zero from recvfrom; socket_recv/HTTPMU/HTTPU propagate zero. Both multicast and response workers exit before validation. recvThread remains non-NULL; no automatic restart. Requires UDP reachability to targeted listener.
Root Cause
Empty datagram yields zero from recvfrom; socket_recv/HTTPMU/HTTPU propagate zero. Both multicast and response workers exit before validation. recvThread remains non-NULL; no automatic restart. Requires UDP reachability to targeted listener.
udp recv — src/mupnp/net/socket.c:966-977
A valid empty UDP datagram produces zero, treated identically to error.
struct sockaddr_storage from;
socklen_t fromLen = sizeof(from);
recvLen = recvfrom(sock->id, recvBuf, sizeof(recvBuf) - 1, 0, (struct sockaddr*)&from, &fromLen);
#endif
mupnp_log_debug_l4("Entering...\n");
if (recvLen <= 0)
return 0;
recvBuf[recvLen] = '\0';
mupnp_socket_datagram_packet_setdata(dgmPkt, recvBuf);
ssdp zero — src/mupnp/ssdp/ssdp_server.c:148-153
Zero-length receive causes the multicast worker to stop.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpmu_socket_recv(server->httpmuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
ssdp response zero — src/mupnp/ssdp/ssdp_response_server.c:150-157
The unicast response worker also exits on an empty datagram.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpu_socket_recv(server->httpuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_packet_print(ssdpPkt);
mupnp_ssdpresponse_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
Validation
Parent source review confirms the input-to-control-to-outcome path. Empty datagram yields zero from recvfrom; socket_recv/HTTPMU/HTTPU propagate zero. Both multicast and response workers exit before validation. recvThread remains non-NULL; no automatic restart. Requires UDP reachability to targeted listener.
Validation method: static source trace
udp recv — src/mupnp/net/socket.c:966-977
A valid empty UDP datagram produces zero, treated identically to error.
struct sockaddr_storage from;
socklen_t fromLen = sizeof(from);
recvLen = recvfrom(sock->id, recvBuf, sizeof(recvBuf) - 1, 0, (struct sockaddr*)&from, &fromLen);
#endif
mupnp_log_debug_l4("Entering...\n");
if (recvLen <= 0)
return 0;
recvBuf[recvLen] = '\0';
mupnp_socket_datagram_packet_setdata(dgmPkt, recvBuf);
ssdp zero — src/mupnp/ssdp/ssdp_server.c:148-153
Zero-length receive causes the multicast worker to stop.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpmu_socket_recv(server->httpmuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
ssdp response zero — src/mupnp/ssdp/ssdp_response_server.c:150-157
The unicast response worker also exits on an empty datagram.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpu_socket_recv(server->httpuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_packet_print(ssdpPkt);
mupnp_ssdpresponse_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
Limitations:
- No application code, exploit input or live network test was executed.
Dataflow
udp-recv -> ssdp-zero -> ssdp-response-zero
udp recv — src/mupnp/net/socket.c:966-977
A valid empty UDP datagram produces zero, treated identically to error.
struct sockaddr_storage from;
socklen_t fromLen = sizeof(from);
recvLen = recvfrom(sock->id, recvBuf, sizeof(recvBuf) - 1, 0, (struct sockaddr*)&from, &fromLen);
#endif
mupnp_log_debug_l4("Entering...\n");
if (recvLen <= 0)
return 0;
recvBuf[recvLen] = '\0';
mupnp_socket_datagram_packet_setdata(dgmPkt, recvBuf);
ssdp zero — src/mupnp/ssdp/ssdp_server.c:148-153
Zero-length receive causes the multicast worker to stop.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpmu_socket_recv(server->httpmuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
ssdp response zero — src/mupnp/ssdp/ssdp_response_server.c:150-157
The unicast response worker also exits on an empty datagram.
while (mupnp_thread_isrunnable(thread) == true) {
if (mupnp_httpu_socket_recv(server->httpuSock, ssdpPkt) <= 0)
break;
mupnp_ssdp_packet_print(ssdpPkt);
mupnp_ssdpresponse_server_performlistener(server, ssdpPkt);
mupnp_ssdp_packet_clear(ssdpPkt);
Reachability
Impact is limited to the targeted SSDP listener; host network reachability is required.
Severity
Low — Impact is limited to the targeted SSDP listener; host network reachability is required.
Additional runtime or deployment evidence could raise or lower this severity.
Impact assessment:
- Level: low
- Why: Impact is limited to the targeted SSDP listener; host network reachability is required.
Likelihood assessment:
- Level: high
- Why: Impact is limited to the targeted SSDP listener; host network reachability is required.
Remediation
Discard empty datagrams and distinguish them from socket errors; keep listener running.
Tests:
- Discard empty datagrams and distinguish them from socket errors; keep listener running. Verify malformed input fails cleanly and valid traffic still works.
Preventive controls:
- Make the repaired invariant mandatory in the shared helper.
Reviewed Surfaces
| Surface | Risk Area | Outcome | Notes |
| Native HTTP, SSDP, POSIX lifecycle and TLS | not recorded | Reported | Seven baseline candidates independently validated. Severity calibrated to process versus subsystem impact and optional backend/lifecycle prerequisites. |
| Legacy media filesystem and XML ownership checks | not recorded | Rejected | Filesystem URI suffix selects an existing MD5 content ID; fopen uses stored scanner-owned path (cdms_filesys_http.c:80-96,130-133). RSS enclosure URL is hashed to objectID before shell filename arguments (crss.c:158-161; cdms_youtube_update.c:298-320), rejecting traced shell-injection lead. AV CGXmlNode copies parsed nodes before parser tree deletion (CGXmlNode.m:37-43,101-108). Operator stdin overflow in upnpdump.c:333-356 crosses no established privilege boundary. |
| Expat and libxml2 XML parsing, SOAP/control and URI utilities | not recorded | Reported | Two parent-validated XML findings. URI percent-escape OOB lead rejected: network strings are NUL terminated and short-circuit evaluation prevents the suspected trailing-character read. Libxml2 predefined-entity callback is present; external-entity disclosure was not established. One investigator final response was access-filtered; earlier observations and returned source coverage were retained. |
| Objective-C wrappers and cache/callback lifetimes | not recorded | Reported | Two independently validated lifetime findings. Retaining an Objective-C wrapper does not preserve its borrowed native object; raw callback userdata also needs a separate registration lifetime. |
| Media traversal examples and network test-device handler | not recorded | Reported | Container traversal and test-only stack disclosure calibrated to conditional utility/test exposure. Sibling traversal operations retained in source evidence. |
| Public API macros, limits and static service descriptors | not recorded | No issue found | Remaining headers reviewed. No new demonstrated boundary violation. Thread header promises termination before deletion; fixed-delay implementation contradicts it and is already reported. Fixed XML service descriptors contain no attacker-selected format/evaluation. Body, header, client and subscriber limits are absent; do not infer that buffer-size constants supply aggregate limits. |
| Build generators and CI publication routes | not recorded | No issue found | Fixed bootstrap/generator shell and Perl commands require trusted repository/operator authority. CI build executes PR code but master-only documentation publication is distinct. CMake emits CG_* flags while consumers use MUPNP_*; preserved as configuration discrepancy, not evidence of activated TLS or XML guards. |
Open Questions And Follow Up
- Historical std/av YouTube target uses inconsistent legacy Cg_* symbols/include paths. Raw URI suffix reaches unbounded strcpy into a small stack buffer at std/av/sample/upnpavserver/youtube/cdms_youtube_http.c:45,77-79, but working compilation/deployment was not established. Root builds omit std/av; preserve as deferred proof gap.
- CGUpnpDevice.m:388 uses uninitialized upnpAction in static delegate callback, but registration precedes service creation and later supported registration was not found.
- Actual deployment, OS permissions, external dependencies and effective flags were not supplied.
- Executable bodies but not every fixed literal were reviewed in: examples/binarylight/binarylight_device.c, examples/clock/clock_device.c, test/TestDevice.c, std/av/src/cybergarage/upnp/std/av/renderer/cavtransport_service.c, std/av/src/cybergarage/upnp/std/av/renderer/cconnectionmgrr_service.c, std/av/src/cybergarage/upnp/std/av/renderer/cmediarenderer_device.c, std/av/src/cybergarage/upnp/std/av/renderer/crenderingcontrol_service.c, std/av/src/cybergarage/upnp/std/av/server/cconnectionmgr_service.c, std/av/src/cybergarage/upnp/std/av/server/ccontentdir_service.c, std/av/src/cybergarage/upnp/std/av/server/cmediaserver_device.c. Ancillary historical project configurations and non-implementation artifacts remain outside full source coverage.
- Working compilation and active deployment of legacy target unresolved; not a validated current-library finding.
- Follow-up prompt: Review deferred unit legacy-youtube-http-buffer and close its stated proof gap.