php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #28673 readfile() crashes on huge local files
Submitted: 2004-06-07 15:46 UTC Modified: 2004-12-21 01:00 UTC
Votes:2
Avg. Score:4.5 ± 0.5
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:1 (50.0%)
From: valyala at tut dot by Assigned:
Status: No Feedback Package: Output Control
PHP Version: 4.3.7 OS: any with MMAP support
Private report: No CVE-ID: None
 [2004-06-07 15:46 UTC] valyala at tut dot by
Description:
------------
When I try to print huge files (greater than 500Mb) using readfile() function, my computer crashes.

I found in the PHP 4.3.7 sources the file /main/strems.c and function _php_stream_passthru() in it.
 The readfile() uses this function to print content of the file.
Below you can see source of the function with my comments:
===================================================================
PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
{
    size_t bcount = 0;
    int ready = 0;
    char buf[8192];
#ifdef HAVE_MMAP
    int fd;
#endif
 
#ifdef HAVE_MMAP
    if (!php_stream_is(stream, PHP_STREAM_IS_SOCKET)
            && stream->filterhead == NULL
            && php_stream_tell(stream) == 0
            && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fd, 0))
    {
        struct stat sbuf;
        off_t off; /* !!! offset == 0 (see above condition [php_stream_tell(stream) == 0] ) */
        void *p;
        size_t len;
 
        fstat(fd, &sbuf); /* !!! missing error check after this line */
 
        if (sbuf.st_size > sizeof(buf)) {
            off = php_stream_tell(stream); /* !!! offset == 0 (see above) */
            len = sbuf.st_size - off;
            /* suppose len > 1 Gb, machine has 128Mb RAM and 128Mb swap. What happens after the next 
line? */
            p = mmap(0, len, PROT_READ, MAP_SHARED, fd, off); /* !!! why MAP_SHARED, not MAP_PRIVATE ?
                            First parameter of the mmap is (void *) type, not (int) */
            if (p != (void *) MAP_FAILED) {
                BG(mmap_file) = p; /* !!! what sense of this and next string? Thread safety?
                        I don't understand how it works here */
                BG(mmap_len) = len;
                PHPWRITE(p, len);
                BG(mmap_file) = NULL; /* !!! thread safety? ok. why there is not BG(mmap_len) = 0 on 
the next line ? */
                munmap(p, len); /* !!! missing error check after munmap */
                bcount += len;
                ready = 1;
            }
        }
    }
#endif
    if(!ready) {
        int b;
 
        while ((b = php_stream_read(stream, buf, sizeof(buf))) > 0) {
            PHPWRITE(buf, b);
            bcount += b;
        }
    }
    return bcount;
}
===================================================================
 
And here you can see my version of the function:
 
===================================================================
PHPAPI size_t _php_stream_passthru(php_stream * stream STREAMS_DC TSRMLS_DC)
{
    size_t bcount = 0; /* counter of printed out bytes */
    int is_mapped = 0;
    char buf[8192];
    size_t buf_len = sizeof(buf);
#ifdef HAVE_MMAP
    int fd;
 
    if (!php_stream_is(stream, PHP_STREAM_IS_SOCKET)
            && stream->filterhead == NULL
            && php_stream_tell(stream) == 0
            && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fd, 0))
    {
        is_mapped = 1;
        buf_len = 1024 * 1024; /* default length of the mapped memory */
        struct stat sbuf;
        void *p; /* pinter to the mapped part of file */
        size_t len;
        /* get the length of local file connected to descriptor fd */
        fstat(fd, &sbuf);
        if (errno) {
             /* cannot get length of file */
            php_error_docref(NULL TSRMLS_CC, E_ERROR, "cannot get length of the file");
            return bcount;
        }
        len = (size_t) sbuf.st_size;
        /* print to the output buffer file contents */
        while (bcount < len) {
            if (len - bcount < buf_len) buf_len = len - bcount;
            p = mmap(NULL, buf_len, PROT_READ, MAP_PRIVATE, fd, (off_t) bcount); /* try to map part of 
the file to memory */
            if (p == (void *) MAP_FAILED) {
                /* error when mapping part of the file to memory */
                php_error_docref(NULL TSRMLS_CC, E_ERROR, "mmap error: cannot map part of the file to 
memory");
                break;
            }
            PHPWRITE(p, buf_len);
            munmap(p, buf_len); /* try to unmap allocated memory */
            if (errno) {
                /* error when unmapping memory */
                php_error_docref(NULL TSRMLS_CC, E_ERROR, "mmap error: cannot unmap allocated memory");
                break;
            }
            bcount += buf_len;
        }
    }
#endif
    if (!is_mapped) {
        /* print to the output buffer stream contents */
        while ((buf_len = php_stream_read(stream, buf, sizeof(buf))) > 0) {
            PHPWRITE(buf, buf_len);
            bcount += buf_len;
        }
    }
    return bcount;
}

Reproduce code:
---------------
<?php
readfile('very_big_file');
?>

Expected result:
----------------
contents of the very_big_file

Actual result:
--------------
PHP crash

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2004-12-13 01:30 UTC] sniper@php.net
Is this still a problem in the most recent PHP versions? (PHP 4.3.9 for example) If it is, please provide an unified diff of your changes (diff -u) and place the patch somewhere in the web and paste an url here.

 [2004-12-21 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 12:01:31 2024 UTC