Skip to content

Commit c5f1b38

Browse files
committed
Move shebang handling into the lexer
Instead of handling shebang lines by adjusting the file pointer in individual SAPIs, move the handling into the lexer, where this is both a lot simpler and more robust. Whether the shebang should be skipped is controlled by CG(skip_shebang) -- we might want to do that in more cases. This fixed bugs #60677 and #78066.
1 parent 17d4e86 commit c5f1b38

File tree

9 files changed

+48
-170
lines changed

9 files changed

+48
-170
lines changed

NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ PHP NEWS
44

55
- Core:
66
. Fixed bug #78212 (Segfault in built-in webserver). (cmb)
7+
. Fixed bug #60677 (CGI doesn't properly validate shebang line contains #!).
8+
(Nikita)
9+
. Fixed bug #78066 (PHP eats the first byte of a program that comes from
10+
process substitution). (Nikita)
711

812
- Libxml:
913
. Fixed bug #78279 (libxml_disable_entity_loader settings is shared between

Zend/zend_compile.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ void zend_init_compiler_data_structures(void) /* {{{ */
329329
zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op));
330330
CG(active_class_entry) = NULL;
331331
CG(in_compilation) = 0;
332-
CG(start_lineno) = 0;
332+
CG(skip_shebang) = 0;
333333

334334
CG(encoding_declared) = 0;
335335
CG(memoized_exprs) = NULL;

Zend/zend_globals.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct _zend_compiler_globals {
9393

9494
struct _zend_ini_parser_param *ini_parser_param;
9595

96-
uint32_t start_lineno;
96+
zend_bool skip_shebang;
9797
zend_bool increment_lineno;
9898

9999
zend_string *doc_comment;

Zend/zend_language_scanner.l

+20-17
Original file line numberDiff line numberDiff line change
@@ -513,16 +513,9 @@ ZEND_API int zend_multibyte_set_filter(const zend_encoding *onetime_encoding)
513513
ZEND_API int open_file_for_scanning(zend_file_handle *file_handle)
514514
{
515515
char *buf;
516-
size_t size, offset = 0;
516+
size_t size;
517517
zend_string *compiled_filename;
518518

519-
/* The shebang line was read, get the current position to obtain the buffer start */
520-
if (CG(start_lineno) == 2 && file_handle->type == ZEND_HANDLE_FP && file_handle->handle.fp) {
521-
if ((offset = ftell(file_handle->handle.fp)) == (size_t)-1) {
522-
offset = 0;
523-
}
524-
}
525-
526519
if (zend_stream_fixup(file_handle, &buf, &size) == FAILURE) {
527520
return FAILURE;
528521
}
@@ -556,13 +549,18 @@ ZEND_API int open_file_for_scanning(zend_file_handle *file_handle)
556549
size = SCNG(script_filtered_size);
557550
}
558551
}
559-
SCNG(yy_start) = (unsigned char *)buf - offset;
552+
SCNG(yy_start) = (unsigned char *)buf;
560553
yy_scan_buffer(buf, (unsigned int)size);
561554
} else {
562555
zend_error_noreturn(E_COMPILE_ERROR, "zend_stream_mmap() failed");
563556
}
564557

565-
BEGIN(INITIAL);
558+
if (CG(skip_shebang)) {
559+
CG(skip_shebang) = 0;
560+
BEGIN(SHEBANG);
561+
} else {
562+
BEGIN(INITIAL);
563+
}
566564

567565
if (file_handle->opened_path) {
568566
compiled_filename = zend_string_copy(file_handle->opened_path);
@@ -573,14 +571,8 @@ ZEND_API int open_file_for_scanning(zend_file_handle *file_handle)
573571
zend_set_compiled_filename(compiled_filename);
574572
zend_string_release_ex(compiled_filename, 0);
575573

576-
if (CG(start_lineno)) {
577-
CG(zend_lineno) = CG(start_lineno);
578-
CG(start_lineno) = 0;
579-
} else {
580-
CG(zend_lineno) = 1;
581-
}
582-
583574
RESET_DOC_COMMENT();
575+
CG(zend_lineno) = 1;
584576
CG(increment_lineno) = 0;
585577
return SUCCESS;
586578
}
@@ -2009,6 +2001,17 @@ string:
20092001
RETURN_TOKEN(T_NS_C);
20102002
}
20112003

2004+
<SHEBANG>"#!" .* {NEWLINE} {
2005+
CG(zend_lineno)++;
2006+
BEGIN(INITIAL);
2007+
goto restart;
2008+
}
2009+
2010+
<SHEBANG>{ANY_CHAR} {
2011+
yyless(0);
2012+
BEGIN(INITIAL);
2013+
goto restart;
2014+
}
20122015

20132016
<INITIAL>"<?=" {
20142017
BEGIN(ST_IN_SCRIPTING);

main/main.c

+4-6
Original file line numberDiff line numberDiff line change
@@ -2644,15 +2644,13 @@ PHPAPI int php_execute_script(zend_file_handle *primary_file)
26442644

26452645
/*
26462646
If cli primary file has shabang line and there is a prepend file,
2647-
the `start_lineno` will be used by prepend file but not primary file,
2647+
the `skip_shebang` will be used by prepend file but not primary file,
26482648
save it and restore after prepend file been executed.
26492649
*/
2650-
if (CG(start_lineno) && prepend_file_p) {
2651-
int orig_start_lineno = CG(start_lineno);
2652-
2653-
CG(start_lineno) = 0;
2650+
if (CG(skip_shebang) && prepend_file_p) {
2651+
CG(skip_shebang) = 0;
26542652
if (zend_execute_scripts(ZEND_REQUIRE, NULL, 1, prepend_file_p) == SUCCESS) {
2655-
CG(start_lineno) = orig_start_lineno;
2653+
CG(skip_shebang) = 1;
26562654
retval = (zend_execute_scripts(ZEND_REQUIRE, NULL, 2, primary_file, append_file_p) == SUCCESS);
26572655
}
26582656
} else {

sapi/cgi/cgi_main.c

+1-72
Original file line numberDiff line numberDiff line change
@@ -2550,78 +2550,7 @@ consult the installation file that came with this distribution, or visit \n\
25502550
}
25512551

25522552
if (CGIG(check_shebang_line)) {
2553-
/* #!php support */
2554-
switch (file_handle.type) {
2555-
case ZEND_HANDLE_FD:
2556-
if (file_handle.handle.fd < 0) {
2557-
break;
2558-
}
2559-
file_handle.type = ZEND_HANDLE_FP;
2560-
file_handle.handle.fp = fdopen(file_handle.handle.fd, "rb");
2561-
/* break missing intentionally */
2562-
case ZEND_HANDLE_FP:
2563-
if (!file_handle.handle.fp ||
2564-
(file_handle.handle.fp == stdin)) {
2565-
break;
2566-
}
2567-
c = fgetc(file_handle.handle.fp);
2568-
if (c == '#') {
2569-
while (c != '\n' && c != '\r' && c != EOF) {
2570-
c = fgetc(file_handle.handle.fp); /* skip to end of line */
2571-
}
2572-
/* handle situations where line is terminated by \r\n */
2573-
if (c == '\r') {
2574-
if (fgetc(file_handle.handle.fp) != '\n') {
2575-
zend_long pos = zend_ftell(file_handle.handle.fp);
2576-
zend_fseek(file_handle.handle.fp, pos - 1, SEEK_SET);
2577-
}
2578-
}
2579-
CG(start_lineno) = 2;
2580-
} else {
2581-
rewind(file_handle.handle.fp);
2582-
}
2583-
break;
2584-
case ZEND_HANDLE_STREAM:
2585-
c = php_stream_getc((php_stream*)file_handle.handle.stream.handle);
2586-
if (c == '#') {
2587-
while (c != '\n' && c != '\r' && c != EOF) {
2588-
c = php_stream_getc((php_stream*)file_handle.handle.stream.handle); /* skip to end of line */
2589-
}
2590-
/* handle situations where line is terminated by \r\n */
2591-
if (c == '\r') {
2592-
if (php_stream_getc((php_stream*)file_handle.handle.stream.handle) != '\n') {
2593-
zend_off_t pos = php_stream_tell((php_stream*)file_handle.handle.stream.handle);
2594-
php_stream_seek((php_stream*)file_handle.handle.stream.handle, pos - 1, SEEK_SET);
2595-
}
2596-
}
2597-
CG(start_lineno) = 2;
2598-
} else {
2599-
php_stream_rewind((php_stream*)file_handle.handle.stream.handle);
2600-
}
2601-
break;
2602-
case ZEND_HANDLE_MAPPED:
2603-
if (file_handle.handle.stream.mmap.buf[0] == '#') {
2604-
size_t i = 1;
2605-
2606-
c = file_handle.handle.stream.mmap.buf[i++];
2607-
while (c != '\n' && c != '\r' && i < file_handle.handle.stream.mmap.len) {
2608-
c = file_handle.handle.stream.mmap.buf[i++];
2609-
}
2610-
if (c == '\r') {
2611-
if (i < file_handle.handle.stream.mmap.len && file_handle.handle.stream.mmap.buf[i] == '\n') {
2612-
i++;
2613-
}
2614-
}
2615-
if(i > file_handle.handle.stream.mmap.len) {
2616-
i = file_handle.handle.stream.mmap.len;
2617-
}
2618-
file_handle.handle.stream.mmap.buf += i;
2619-
file_handle.handle.stream.mmap.len -= i;
2620-
}
2621-
break;
2622-
default:
2623-
break;
2624-
}
2553+
CG(skip_shebang) = 1;
26252554
}
26262555

26272556
switch (behavior) {

sapi/cgi/tests/bug60677.phpt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Bug #60677: CGI doesn't properly validate shebang line contains #!
3+
--CGI--
4+
--FILE--
5+
#<?php echo "Hello World\n"; ?>
6+
Second line.
7+
--EXPECT--
8+
#Hello World
9+
Second line.

sapi/cli/php_cli.c

+7-27
Original file line numberDiff line numberDiff line change
@@ -587,12 +587,9 @@ static const char *param_mode_conflict = "Either execute direct code, process st
587587

588588
/* {{{ cli_seek_file_begin
589589
*/
590-
static int cli_seek_file_begin(zend_file_handle *file_handle, char *script_file, int *lineno)
590+
static int cli_seek_file_begin(zend_file_handle *file_handle, char *script_file)
591591
{
592-
int c;
593-
594-
*lineno = 1;
595-
592+
// TODO: Is this still needed?
596593
file_handle->type = ZEND_HANDLE_FP;
597594
file_handle->opened_path = NULL;
598595
file_handle->free_filename = 0;
@@ -602,23 +599,7 @@ static int cli_seek_file_begin(zend_file_handle *file_handle, char *script_file,
602599
}
603600
file_handle->filename = script_file;
604601

605-
/* #!php support */
606-
c = fgetc(file_handle->handle.fp);
607-
if (c == '#' && (c = fgetc(file_handle->handle.fp)) == '!') {
608-
while (c != '\n' && c != '\r' && c != EOF) {
609-
c = fgetc(file_handle->handle.fp); /* skip to end of line */
610-
}
611-
/* handle situations where line is terminated by \r\n */
612-
if (c == '\r') {
613-
if (fgetc(file_handle->handle.fp) != '\n') {
614-
zend_long pos = zend_ftell(file_handle->handle.fp);
615-
zend_fseek(file_handle->handle.fp, pos - 1, SEEK_SET);
616-
}
617-
}
618-
*lineno = 2;
619-
} else {
620-
rewind(file_handle->handle.fp);
621-
}
602+
rewind(file_handle->handle.fp);
622603

623604
return SUCCESS;
624605
}
@@ -649,7 +630,6 @@ static int do_cli(int argc, char **argv) /* {{{ */
649630
char *arg_free=NULL, **arg_excp=&arg_free;
650631
char *script_file=NULL, *translated_path = NULL;
651632
int interactive=0;
652-
int lineno = 0;
653633
const char *param_error=NULL;
654634
int hide_argv = 0;
655635

@@ -922,7 +902,7 @@ static int do_cli(int argc, char **argv) /* {{{ */
922902
php_optind++;
923903
}
924904
if (script_file) {
925-
if (cli_seek_file_begin(&file_handle, script_file, &lineno) != SUCCESS) {
905+
if (cli_seek_file_begin(&file_handle, script_file) != SUCCESS) {
926906
goto err;
927907
} else {
928908
char real_path[MAXPATHLEN];
@@ -960,7 +940,7 @@ static int do_cli(int argc, char **argv) /* {{{ */
960940
goto err;
961941
}
962942
request_started = 1;
963-
CG(start_lineno) = lineno;
943+
CG(skip_shebang) = 1;
964944

965945
zend_register_bool_constant(
966946
ZEND_STRL("PHP_CLI_PROCESS_TITLE"),
@@ -1050,10 +1030,10 @@ static int do_cli(int argc, char **argv) /* {{{ */
10501030
}
10511031
} else {
10521032
if (script_file) {
1053-
if (cli_seek_file_begin(&file_handle, script_file, &lineno) != SUCCESS) {
1033+
if (cli_seek_file_begin(&file_handle, script_file) != SUCCESS) {
10541034
exit_status = 1;
10551035
} else {
1056-
CG(start_lineno) = lineno;
1036+
CG(skip_shebang) = 1;
10571037
php_execute_script(&file_handle);
10581038
exit_status = EG(exit_status);
10591039
}

sapi/phpdbg/phpdbg_prompt.c

+1-46
Original file line numberDiff line numberDiff line change
@@ -567,62 +567,17 @@ int phpdbg_compile(void) /* {{{ */
567567
{
568568
zend_file_handle fh;
569569
char *buf;
570-
char *start_line = NULL;
571570
size_t len;
572-
size_t start_line_len;
573-
int i;
574571

575572
if (!PHPDBG_G(exec)) {
576573
phpdbg_error("inactive", "type=\"nocontext\"", "No execution context");
577574
return FAILURE;
578575
}
579576

580577
if (php_stream_open_for_zend_ex(PHPDBG_G(exec), &fh, USE_PATH|STREAM_OPEN_FOR_INCLUDE) == SUCCESS && zend_stream_fixup(&fh, &buf, &len) == SUCCESS) {
581-
/* Skip #! line */
582-
if (len >= 3 && buf[0] == '#' && buf[1] == '!') {
583-
char *end = buf + len;
584-
do {
585-
switch (fh.handle.stream.mmap.buf++[0]) {
586-
case '\r':
587-
if (fh.handle.stream.mmap.buf[0] == '\n') {
588-
fh.handle.stream.mmap.buf++;
589-
}
590-
case '\n':
591-
CG(start_lineno) = 2;
592-
start_line_len = fh.handle.stream.mmap.buf - buf;
593-
start_line = emalloc(start_line_len);
594-
memcpy(start_line, buf, start_line_len);
595-
fh.handle.stream.mmap.len -= start_line_len;
596-
end = fh.handle.stream.mmap.buf;
597-
}
598-
} while (fh.handle.stream.mmap.buf + 1 < end);
599-
}
600-
578+
CG(skip_shebang) = 1;
601579
PHPDBG_G(ops) = zend_compile_file(&fh, ZEND_INCLUDE);
602580

603-
/* prepend shebang line to file_source */
604-
if (start_line) {
605-
phpdbg_file_source *data = zend_hash_find_ptr(&PHPDBG_G(file_sources), PHPDBG_G(ops)->filename);
606-
607-
dtor_func_t dtor = PHPDBG_G(file_sources).pDestructor;
608-
PHPDBG_G(file_sources).pDestructor = NULL;
609-
zend_hash_del(&PHPDBG_G(file_sources), PHPDBG_G(ops)->filename);
610-
PHPDBG_G(file_sources).pDestructor = dtor;
611-
612-
data = erealloc(data, sizeof(phpdbg_file_source) + sizeof(uint32_t) * ++data->lines);
613-
memmove(data->line + 1, data->line, sizeof(uint32_t) * data->lines);
614-
data->line[0] = 0;
615-
data->buf = erealloc(data->buf, data->len + start_line_len);
616-
memmove(data->buf + start_line_len, data->buf, data->len);
617-
memcpy(data->buf, start_line, start_line_len);
618-
efree(start_line);
619-
data->len += start_line_len;
620-
for (i = 1; i <= data->lines; i++) {
621-
data->line[i] += start_line_len;
622-
}
623-
zend_hash_update_ptr(&PHPDBG_G(file_sources), PHPDBG_G(ops)->filename, data);
624-
}
625-
626581
fh.handle.stream.mmap.buf = buf;
627582
fh.handle.stream.mmap.len = len;
628583
zend_destroy_file_handle(&fh);

0 commit comments

Comments
 (0)