Bug #71384 [Opn]: fread() does not detect file access error

From: Date: Sat, 09 Dec 2017 10:47:49 +0000
Subject: Bug #71384 [Opn]: fread() does not detect file access error
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-213025@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=71384&edit=1

 ID:                 71384
 User updated by:    salsi at icosaedro dot it
 Reported by:        salsi at icosaedro dot it
 Summary:            fread() does not detect file access error
 Status:             Open
 Type:               Bug
 Package:            Streams related
 Operating System:   Slackware 14.1
 PHP Version:        master-Git-2016-01-15 (Git)
 Block user comment: N
 Private report:     N

 New Comment:

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).


Previous Comments:
------------------------------------------------------------------------
[2017-12-09 10:47:44] 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).

------------------------------------------------------------------------
[2017-12-07 17:15:01] ab@php.net

fopen() should have failed in first place, but this check is missing in the php code.

Thanks.

------------------------------------------------------------------------
[2016-01-16 15:14:30] 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)));
?>

------------------------------------------------------------------------
[2016-01-16 13:20:07] 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-15 23:23:19] 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



------------------------------------------------------------------------



--
Edit this bug report at https://bugs.php.net/bug.php?id=71384&edit=1


Thread (6 messages)

« previous php.bugs (#213025) next »