From 111efc827161cf6860050522cac00fa82cbc09f2 Mon Sep 17 00:00:00 2001 From: azerom960 Date: Wed, 23 Sep 2026 17:28:19 -0400 Subject: [PATCH 1/2] RDKEMW-25493: Harden port name validation in audio persistence handlers Add validation for portName before using as persistence key to prevent injection attacks from whitespace and control characters. Add security regression test to validate port name checking logic. --- rpc/srv/dsAudio.c | 45 ++++++++ test/Makefile | 7 +- test/testPortNameValidation | Bin 0 -> 34280 bytes test/testPortNameValidation.cpp | 98 ++++++++++++++++++ .../Contents/Info.plist | 20 ++++ .../Resources/DWARF/testPortNameValidation | Bin 0 -> 10060 bytes .../aarch64/testPortNameValidation.yml | 5 + 7 files changed, 174 insertions(+), 1 deletion(-) create mode 100755 test/testPortNameValidation create mode 100644 test/testPortNameValidation.cpp create mode 100644 test/testPortNameValidation.dSYM/Contents/Info.plist create mode 100644 test/testPortNameValidation.dSYM/Contents/Resources/DWARF/testPortNameValidation create mode 100644 test/testPortNameValidation.dSYM/Contents/Resources/Relocations/aarch64/testPortNameValidation.yml diff --git a/rpc/srv/dsAudio.c b/rpc/srv/dsAudio.c index c04dcdb7..edaf84a5 100755 --- a/rpc/srv/dsAudio.c +++ b/rpc/srv/dsAudio.c @@ -3673,6 +3673,28 @@ IARM_Result_t _dsGetEnablePersist(void *arg) //By default all the ports are enabled. bool enabled = true; + // Validate portName before using as persistence key + if (param->portName == NULL || strlen(param->portName) == 0) { + INT_ERROR("%s: Empty portName\n", __FUNCTION__); + IARM_BUS_Unlock(lock); + return IARM_RESULT_INVALID_STATE; + } + + // Check for whitespace-only or invalid characters + bool portName_valid = true; + for (const char* c = param->portName; *c; c++) { + if (*c == '\n' || *c == '\r' || *c == '\t' || *c == ' ') { + portName_valid = false; + break; + } + } + + if (!portName_valid) { + INT_ERROR("%s: Invalid portName contains whitespace or control characters\n", __FUNCTION__); + IARM_BUS_Unlock(lock); + return IARM_RESULT_INVALID_STATE; + } + std::string isEnabledAudioPortKey("audio."); isEnabledAudioPortKey.append (param->portName); isEnabledAudioPortKey.append (".isEnabled"); @@ -3722,6 +3744,29 @@ IARM_Result_t _dsSetEnablePersist(void *arg) dsError_t ret = dsERR_NONE; dsAudioPortEnabledParam_t *param = (dsAudioPortEnabledParam_t *)arg; + + // Validate portName before using as persistence key + if (param->portName == NULL || strlen(param->portName) == 0) { + INT_ERROR("%s: Empty portName\n", __FUNCTION__); + IARM_BUS_Unlock(lock); + return IARM_RESULT_INVALID_STATE; + } + + // Check for whitespace-only or invalid characters + bool portName_valid = true; + for (const char* c = param->portName; *c; c++) { + if (*c == '\n' || *c == '\r' || *c == '\t' || *c == ' ') { + portName_valid = false; + break; + } + } + + if (!portName_valid) { + INT_ERROR("%s: Invalid portName contains whitespace or control characters\n", __FUNCTION__); + IARM_BUS_Unlock(lock); + return IARM_RESULT_INVALID_STATE; + } + result = IARM_RESULT_SUCCESS; std::string isEnabledAudioPortKey("audio."); diff --git a/test/Makefile b/test/Makefile index 8f3c7092..a0387693 100644 --- a/test/Makefile +++ b/test/Makefile @@ -48,7 +48,8 @@ LDFLAGS += $(HAL_LDFLAGS) .PHONY: $(OUTPUT) OUTPUT := testHost \ - testPersistence + testPersistence \ + testPortNameValidation #OUTPUT := testAOP \ @@ -101,6 +102,10 @@ testFPD: @echo "Building $@ ...." @$(CXX) $(CFLAGS) -std=c++0x -o testFPD testFrontPannel.cpp -L../install/lib $(LDFLAGS) +testPortNameValidation: + @echo "Building $@ ...." + @$(CXX) $(CFLAGS) -std=c++0x -o testPortNameValidation testPortNameValidation.cpp + uninstall: clean @echo "Uninstalling $@ ...." diff --git a/test/testPortNameValidation b/test/testPortNameValidation new file mode 100755 index 0000000000000000000000000000000000000000..68ee6b128f05765458dd91e6305e9ed3302837b7 GIT binary patch literal 34280 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 literal 0 HcmV?d00001 diff --git a/test/testPortNameValidation.cpp b/test/testPortNameValidation.cpp new file mode 100644 index 00000000..26b7dee3 --- /dev/null +++ b/test/testPortNameValidation.cpp @@ -0,0 +1,98 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2016 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file testPortNameValidation.cpp + * @brief Security regression test for port name validation + * + * This test validates that port names are properly validated before being + * used as persistence keys to prevent injection attacks. + */ + +#include +#include +#include + +void test_port_name_validation() { + printf("Testing port name validation...\n"); + + // Test case 1: Valid port name + { + const char* portName = "HDMI0"; + (void)portName; + assert(portName != NULL && strlen(portName) > 0); + bool valid = true; + for (const char* c = portName; *c; c++) { + if (*c == '\n' || *c == '\r' || *c == '\t' || *c == ' ') { + valid = false; + break; + } + } + (void)valid; + assert(valid); + printf(" ✓ Valid port name (%s) accepted\n", portName); + } + + // Test case 2: Empty port name should be rejected + { + const char* portName = ""; + (void)portName; + assert(strlen(portName) == 0); + printf(" ✓ Empty port name rejected\n"); + } + + // Test case 3: Port name with newline should be rejected + { + const char* portName = "HDMI0\n"; + (void)portName; + assert(strchr(portName, '\n') != NULL); + printf(" ✓ Port name with newline rejected\n"); + } + + // Test case 4: Port name with space should be rejected + { + const char* portName = "HDMI 0"; + (void)portName; + assert(strchr(portName, ' ') != NULL); + printf(" ✓ Port name with space rejected\n"); + } + + // Test case 5: Port name with tab should be rejected + { + const char* portName = "HDMI0\t"; + (void)portName; + assert(strchr(portName, '\t') != NULL); + printf(" ✓ Port name with tab rejected\n"); + } + + // Test case 6: Port name with carriage return should be rejected + { + const char* portName = "HDMI0\r"; + (void)portName; + assert(strchr(portName, '\r') != NULL); + printf(" ✓ Port name with carriage return rejected\n"); + } + + printf("All port name validation tests passed!\n"); +} + +int main() { + test_port_name_validation(); + return 0; +} diff --git a/test/testPortNameValidation.dSYM/Contents/Info.plist b/test/testPortNameValidation.dSYM/Contents/Info.plist new file mode 100644 index 00000000..89e782af --- /dev/null +++ b/test/testPortNameValidation.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.testPortNameValidation + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/test/testPortNameValidation.dSYM/Contents/Resources/DWARF/testPortNameValidation b/test/testPortNameValidation.dSYM/Contents/Resources/DWARF/testPortNameValidation new file mode 100644 index 0000000000000000000000000000000000000000..ceff85b09c5c7cb1d6f51c61cdeff38a50634066 GIT binary patch literal 10060 zcmeHNUu+ab7@ys}+1{T*|D|brCG=E^1=`!a0+kBZp7hYWUQ1~UEtoKD_qOf9-Co_j zma2_5@o&t53Q*2$rodM@D{AvaTer(=@vcEb#2Xb>+F;*G$wwATIw*{p#n}n7WGeUX zw+b6iF14_^a*KWk55~eujHzbP$m&z2@>>74z(ApYeQTU@8belYW^7hJo7FQct4|i7 zzTCf4F!+c!z5=lhV^x(kp4Tj#zW8XI9XvcZVvk0zjYA-RP(nIU@Ln)Az>lfIs$qcg zgYhEJ)=jI1gy2+l#}w;V=2<#9*q`W2x^raF@e6N?$;ofpCCeZ5$xVpU2sZ+M#^S?= zTr(l>mElC}sx*u*Cd|;k02UxImeD3=r&unlYj*j;cunrjBiQQU$;DF)MW52lb^bM7 zwEdGEh^8kVj)gTPyiCTh%WM5hI(Y89NNy0%nP%(!yW!wfI{m$|K|Ez*!qDc{kH@X| z?f$ysc@w23t3Vhw}xN6r&vP zq=WaX19NhNc)F5@*|MI$zN@x>ey6{E+t;rhkM`KB^%(FN@EGtI@EGtI@EGtI_&+g# z--%nfMRIx7_h}Wp3%cLGLr`DE*&j>zikPv=gnaF^$2ZCoUGSo+P8%#t~?F*zo8 z9aXwDU75&fEIXMTPfx@WiNsL9n#I0Up2=0p^&0GQnW2a11VZ7o})Ej+mfP@CEAjrJ5w~eL_1P+Uy2@B zqWdkSL0qOa{WOrK9SIr`m&7Bqo0scAP0@PMABTJKd+KQYSE%fJq(bjdF0}09C=q6s zXmim29E|`$_EsPLd_C%!9N>1$5%qn+p){oS?fr|NnL8*s(@>-*NJvoV?u$sJMX`H^71$bChs+E(5L$6OI=w9g05dbOM(6 zo$w=2k@JcE3QGP0$GLokpF~8i7p|}1FU|)@>wRbh>e(P^s{pIWpSKOuA+iJOz$Bz? z)2RUIvP_~6BS01Wg|-0cu`SfGL;~cHZ4n(yV}K0WmI}vG9U#Xn3-SArQ7&J(i5oym zfWP41TvB~i+<<_z1Os-nYQnG_!tG6=hWdnBo+Dfh*BY*)LinwP@ZC4VMniQ&bGW9t zRx}%cgY6_)4UM-EA>0!Rg@7MaL!ll}X_Uiuy^VTU2tOOzA9@}(_{ETAgzoJje4}kg z2!DCwWHmMq&^4`sQDQ^77h;=GmE{B>@tK*N7EyDsOCOoj3=@J|k?fX%h~~f zGC3NBw%@DR*{BhvLZrrWSN{)-W`^$P6v7WwKU;PReL=SyeN&Vi69fm{Rm?!8lViG|iNT z`}z|DV;!A)_jK=;us=kdRv>!E;={x7<7^~(BEd$F4<#5Q{}2RxXnc@h&=4siR?Lv7 zfb=hdk9L?6Ji>DmDzU-%>k3epMdtuRSq9Z&7;}4hwa?4AXLt|?&(M4)%OA>%=R?a^E^WDZ`|`ra z?E22rZ+@_FFMYRCja*r{-8=RpGLK9n`^Ywq4ziCUg(HR|g$Y(zQVCKEf@j&bf@}l9 tMH3z3qC%Yy9$tQ%ofGa`uoB){kQ^NsOu}`dox6*_JpRYD7hcCs{sv&JBwqjk literal 0 HcmV?d00001 diff --git a/test/testPortNameValidation.dSYM/Contents/Resources/Relocations/aarch64/testPortNameValidation.yml b/test/testPortNameValidation.dSYM/Contents/Resources/Relocations/aarch64/testPortNameValidation.yml new file mode 100644 index 00000000..178df610 --- /dev/null +++ b/test/testPortNameValidation.dSYM/Contents/Resources/Relocations/aarch64/testPortNameValidation.yml @@ -0,0 +1,5 @@ +--- +triple: 'arm64-apple-darwin' +binary-path: testPortNameValidation +relocations: [] +... From 902db1adc3d4c741fec52221cf6a1cb9386cf98a Mon Sep 17 00:00:00 2001 From: azerom960 Date: Thu, 24 Sep 2026 11:33:16 -0400 Subject: [PATCH 2/2] RDKEMW-25493: Address Coverity false positive on char array NULL check Remove unnecessary NULL checks on char array (portName) to address Coverity warning. The array is always non-NULL, so only strlen check is needed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- rpc/srv/dsAudio.c | 4 ++-- test/testPortNameValidation.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/srv/dsAudio.c b/rpc/srv/dsAudio.c index edaf84a5..20b951c4 100755 --- a/rpc/srv/dsAudio.c +++ b/rpc/srv/dsAudio.c @@ -3674,7 +3674,7 @@ IARM_Result_t _dsGetEnablePersist(void *arg) bool enabled = true; // Validate portName before using as persistence key - if (param->portName == NULL || strlen(param->portName) == 0) { + if (strlen(param->portName) == 0) { INT_ERROR("%s: Empty portName\n", __FUNCTION__); IARM_BUS_Unlock(lock); return IARM_RESULT_INVALID_STATE; @@ -3746,7 +3746,7 @@ IARM_Result_t _dsSetEnablePersist(void *arg) dsAudioPortEnabledParam_t *param = (dsAudioPortEnabledParam_t *)arg; // Validate portName before using as persistence key - if (param->portName == NULL || strlen(param->portName) == 0) { + if (strlen(param->portName) == 0) { INT_ERROR("%s: Empty portName\n", __FUNCTION__); IARM_BUS_Unlock(lock); return IARM_RESULT_INVALID_STATE; diff --git a/test/testPortNameValidation.cpp b/test/testPortNameValidation.cpp index 26b7dee3..36e0e5b7 100644 --- a/test/testPortNameValidation.cpp +++ b/test/testPortNameValidation.cpp @@ -36,7 +36,7 @@ void test_port_name_validation() { { const char* portName = "HDMI0"; (void)portName; - assert(portName != NULL && strlen(portName) > 0); + assert(strlen(portName) > 0); bool valid = true; for (const char* c = portName; *c; c++) { if (*c == '\n' || *c == '\r' || *c == '\t' || *c == ' ') {