php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71384 fread() does not detect file access error
Submitted: 2016-01-15 23:23 UTC Modified: 2019-07-25 09:07 UTC
From: salsi at icosaedro dot it Assigned: nikic (profile)
Status: Closed Package: Streams related
PHP Version: master-Git-2016-01-15 (Git) OS: Slackware 14.1
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: salsi at icosaedro dot it
New email:
PHP Version: OS:

 

 [2016-01-15 23:23 UTC] salsi at icosaedro dot it
Description:
------------
Possibly related: bug #71263 fread() does not detects decoding errors from filter bzip2.decompressed (https://bugs.php.net/bug.php?id=71263).

Trying to read an unreadable file, fread() does not to detect the problem and simply returns the empty string; no error nor exception whatsover.

The simplest way to simulate an unreadable file on Linux is to read /proc/self/mem, a file that can be succesfully openend but cannot be read from the very beginning (credits for this trick: http://unix.stackexchange.com/a/6302):

	$ cat /proc/self/mem
	cat: /proc/self/mem: Input/output error

A simple test made with gcc C confirms that fopen() succeeds while fread() gives input/output error.

Under PHP, instead, no errors are detected, and fread() simply returns the empty string, as the following test script proves:


Test script:
---------------
<?php
echo "PHP version: ", PHP_VERSION, "\n";
// set safe test environment:
error_reporting(-1);
ini_set("track_errors", "1");

// maps errors to ErrorException:
function my_error_handler($errno, $message) {
	throw new ErrorException($message);
}
set_error_handler("my_error_handler");

$f = fopen("/proc/self/mem", "r");
do {
	$bytes = fread($f, 1000);
	echo "bytes=";
	var_dump($bytes);
} while (!feof($f));
fclose($f);
// --> bytes=string(0) "", no error whatsoever

echo "Just testing if error detection is still on:\n";
$f = fopen("this file does not exist!", "rb");
?>

Expected result:
----------------
PHP version: 7.1.0-dev
(exception on the first fread() telling the file is unreadable)

Actual result:
--------------
PHP version: 7.1.0-dev
bytes=string(0) ""
Just testing if error detection is still on:
PHP Fatal error:  Uncaught ErrorException: fopen(this file does not exist!): failed to open stream: No such file or directory in /home/salsi/src/phplint/bug-fread-unreadable-file.php:9
Stack trace:
#0 [internal function]: my_error_handler(2, 'fopen(this file...', '/home/salsi/src...', 23, Array)
#1 /home/salsi/src/phplint/bug-fread-unreadable-file.php(23): fopen('this file does ...', 'rb')
#2 {main}
  thrown in /home/salsi/src/phplint/bug-fread-unreadable-file.php on line 9
Fatal error: Uncaught ErrorException: fopen(this file does not exist!): failed to open stream: No such file or directory in /home/salsi/src/phplint/bug-fread-unreadable-file.php:9
Stack trace:
#0 [internal function]: my_error_handler(2, 'fopen(this file...', '/home/salsi/src...', 23, Array)
#1 /home/salsi/src/phplint/bug-fread-unreadable-file.php(23): fopen('this file does ...', 'rb')
#2 {main}
  thrown in /home/salsi/src/phplint/bug-fread-unreadable-file.php on line 9


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-16 13:20 UTC] danack@php.net
To increase the likelihood of this being fixed, would it be possible for you to attach the C code that you tested with, to show the exact error conditions.

(I'm not saying that will guarantee that it will be fixed, it just makes it a bit more likely.)
 [2016-01-16 15:14 UTC] salsi at icosaedro dot it
Two sources I used to investigate this issue:

- C test program, compiled with gcc under Slackware 14.1 that show how i/o error is detected reading /proc/self/mem.

- PHP program that generates an ext2 disk image corrupted on pourpose on which i/o error might be tested on, as a further example.


// https://bugs.php.net/bug.php?id=71384

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int unbuffered() {
	int fd, n;
	char buf[10000];
	
	fd = open("/proc/self/mem", O_RDONLY);
//	fd = open(__FILE__, O_RDONLY);
	if(fd < 0){
		fprintf(stderr, "open(): %s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	
	n = read(fd, buf, 100);
	printf("read(): %d\n", n);
	if(n < 0){
		fprintf(stderr, "read(): %s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	return (EXIT_SUCCESS);
}


int buffered() {
	FILE * f;
	int n;
	char buf[100];
	
	f = fopen("/proc/self/mem", "r");
//	f = fopen(__FILE__, "r");
	if(f == NULL){
		fprintf(stderr, "fopen(): %s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	
	while(1){
		n = fread(buf, 1, 100, f);
		printf("fread(): %d\n", n);
		if(n <= 0){
			if( ferror(f) == 0 ){
				return EXIT_SUCCESS;
			} else {
				fprintf(stderr, "fread(): %s\n", strerror(errno));
				return EXIT_FAILURE;
			}
		}
	}

	return EXIT_SUCCESS;
}


int main(int argc, char** argv) {
	
//	return unbuffered();
	// Output:
	// read(): -1
	// read(): Input/output error

	
	return buffered();
	// Output:
	// fread(): 0
	// fread() : Input / output error

}



<?php

/**
 * damaged-disk.php
 * 
 * Investigating https://bugs.php.net/bug.php?id=71384
 * 
 * This program creates the file "damaged-disk.img" containing the damaged
 * disk image of a Linux ext2 partition; the partition contains a single
 * file named "badfile" that can read only partially before I/O error:
 * 
 * $ su
 * # mkdir baddisk
 * # mount -r damaged-disk.img baddisk
 * # cat baddisk/badfile
 * cat: baddisk/badfile: Input/output error
 */

$s = <<< EOT
H4sICI1YmlYAA2RhbWFnZWQtZGlzawDt3TloFGEYgOF/NyvRGE3UeN/3EY3xvo23hSCIkEYb
JUaEEBG1EcF4tRZ29iJiL4iVpa0gdhaCraSx0Cp+k0l01aBIjCvM88CbbJJZ9h+Yb7IzzaYE
FFVz1BWNi+ZFlahUvcGivOahH2e9e9BZTgMDJ/tLg9tdf/Ogc3jT4edNjPqi7VF56PeXbs2+
9urJ8fdP77TdPz+l/e6Y7dAv3DvV9+j25mNLn/c3djx7/KE3W1fj0N+q9+NvKv1+E6iZupTP
fD7/lcF5BYphYMiNePxpACiUplovAKiV4fcB2fXvcP/y/ceH/WnwAmTH2x9fv+67a5H6f7ko
CqPvZnxpr1R+Pv5Lo74Wbhnt4hhzL7LzT/tI579ymlW13fhoQtSQ8vub2X3DSdHkqCnl90en
RFOjaX/w+g+z4+/lSOe/Uuqu2m56NCOaGWXrmh3Nieam/L7t/GhBtDDlt2wXVz332rHTn/9g
SRRIdo5rTKVy29fH5XJbW36Mt6SGcs/Fy1dauy9e7e3Kj/nXdfWls2e6ui/0nKvpuoHRy/+X
fZv/j3X5/AMFUan1AoCaMf9QXOYfisv8Q3GZfygu8w/FZf6huMw/FJf5h+Iy/1Bc5h8AAAAA
AAAAAAAAAAAA/itLoqUnUloW35dHK6KV0apodbQmao3WRuui7KNB1qfBj8xKG6KN0aZoc7Ql
2hpti7ZHO6Kd0a5od7Qn2ht1RPui7KO3DkQHo0PR4ehIdHRM9xgAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDYvgAo
nTthAJABAA==
EOT;

file_put_contents("damaged-disk.img", gzdecode(base64_decode($s)));
?>
 [2017-12-07 17:15 UTC] ab@php.net
fopen() should have failed in first place, but this check is missing in the php code.

Thanks.
 [2017-12-09 10:47 UTC] salsi at icosaedro dot it
Bug #71384 [Opn]: fread() does not detect file access error
https://bugs.php.net/bug.php?id=71384&edit=2


Maybe it worth to summarize the general issue emerged from an old thread I started back in 2016-01-21 on the developers' mailing list with subject

"Severe safety fail in file access and stream filters"
https://www.mail-archive.com/internals@lists.php.net/msg83033.html

There, several bugs related to the I/O layer are listed involving files, sockets and filtered streams, which are still all open:

+ Missing error verification after fwrite()
https://bugs.php.net/bug.php?id=39598

+ fread() does not detects decoding errors from filter bzip2.decompress
https://bugs.php.net/bug.php?id=71263

+ fread() does not detect file access error
https://bugs.php.net/bug.php?id=71384

+ fread() does not detect decoding errors from filter zlib.inflate
https://bugs.php.net/bug.php?id=71417

+ require* and include* do not detect input/output error
https://bugs.php.net/bug.php?id=71385

...and maybe much more spread here and there among the libraries, possibly including data base connections and more. Yasuo Ohgaki found that the the origin of the issue has a common denominator, that is errors do not propagate from the libc to the PHP streams interface and get lost with unpredictable results:

> Plain file stream reads data by php_stdiop_read()
> http://lxr.php.net/xref/PHP_5_6/main/streams/plain_wrapper.c#338
> As you can see there is no way to return errors from it. We need
> errno like error handling for PHP streams to propagate errors as well
> as more robust code for unexpected.

Julien Pauli tells there is no a simple fix, and the whole streams library should be re-designed from scratch:

> I think what we should do is sit around the table with people
> interested (Daniel Lowrey may be one of them), and plan a full rewrite
> of streams for PHP next major (PHP 8).
 [2019-07-25 09:07 UTC] nikic@php.net
-Status: Open +Status: Closed -Assigned To: +Assigned To: nikic
 [2019-07-25 09:07 UTC] nikic@php.net
This will generate a

fread(): read of 8192 bytes failed with errno=5 Input/output error

notice (promoted to ErrorException in your example) in PHP 7.4.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 20:01:29 2024 UTC