Skip to content

S_regnode_guts()/S_change_engine_size() are doing for( ; ; ){ was_0x30 = realloc(was_0x30, 0x30); } #23359

Open
@bulk88

Description

@bulk88

Description

win32_realloc() was passed 0x30, HeapSize() on the ptr says the block size is already 0x30.

inside S_change_engine_size

((RExC_state_t *)pRExC_state)->precomp is "(?x) \b strict .pmc? \z"
((RExC_state_t *)pRExC_state)->size is 1
((RExC_state_t *)pRExC_state)->parens_buf_size is 0
((RExC_state_t *)pRExC_state)->maxlen is 0

inside S_change_engine_size input stack argument const ptrdiff_t size is 1

inside STATIC regnode_offset S_regnode_guts(pTHX_ RExC_state_t *pRExC_state, const STRLEN extra_size) c stack input argument extra_size is 0

STATIC regnode_offset
S_regnode_guts_debug(pTHX_ RExC_state_t *pRExC_state, const U8 op, const STRLEN extra_size) {
    PERL_ARGS_ASSERT_REGNODE_GUTS_DEBUG;
    assert(extra_size >= REGNODE_ARG_LEN(op) || REGNODE_TYPE(op) == ANYOF);
    return S_regnode_guts(aTHX_ pRExC_state, extra_size);
}

why is extra_size >= REGNODE_ARG_LEN(op) >= operator and not >?

STATIC regnode_offset
S_regnode_guts(pTHX_ RExC_state_t *pRExC_state, const STRLEN extra_size)
{
    /* Allocate a regnode that is (1 + extra_size) times as big as the
     * smallest regnode worth of space, and also aligns and increments
     * RExC_size appropriately.
     *
     * It returns the regnode's offset into the regex engine program */

    const regnode_offset ret = RExC_emit;

    PERL_ARGS_ASSERT_REGNODE_GUTS;

    SIZE_ALIGN(RExC_size);
    change_engine_size(pRExC_state, (ptrdiff_t) 1 + extra_size);
    NODE_ALIGN_FILL(REGNODE_p(ret));
    return(ret);
}

why does this have no logic to text if old and new sizes are equal? IDK enough to say if "are equal" is before or after whatever SIZE_ALIGN(RExC_size); does.

But now change_engine_size(pRExC_state, (ptrdiff_t) 1 + extra_size); is going to realloc() a ptr that was 0x30 before, with a new length of 0x30.

PL_curcop is pointing to line 13 at

&& __FILE__ =~ ( '(?x) \b (?i:'.__PACKAGE__.') \.pmc? \z' );

  && __FILE__ =~ ( '(?x) \b (?i:'.__PACKAGE__.') \.pmc? \z' );

The script executing is ..\miniperl.exe -I..\lib -f ..\write_buildcustomize.pl .. as the 1st process start ever of a fresh miniperl.exe binary.

callstack of the msize() assert() I added to catch this p = realloc(realloc(p,0x30),0x30); defect.

>	miniperl.exe!win32_realloc(void * block, unsigned __int64 size) Line 5090	C
 	miniperl.exe!Perl_safesysrealloc(void * where, unsigned __int64 size) Line 308	C
 	miniperl.exe!S_change_engine_size(RExC_state_t * pRExC_state, const __int64 size) Line 12821	C
 	miniperl.exe!S_regnode_guts(RExC_state_t * pRExC_state, const unsigned __int64 extra_size) Line 12851	C
 	miniperl.exe!S_regnode_guts_debug(RExC_state_t * pRExC_state, const unsigned char op, const unsigned __int64 extra_size) Line 12862	C
 	miniperl.exe!S_reg_node(RExC_state_t * pRExC_state, unsigned char op) Line 12874	C
 	miniperl.exe!S_regatom(RExC_state_t * pRExC_state, long * flagp, unsigned long depth) Line 5911	C
 	miniperl.exe!S_regpiece(RExC_state_t * pRExC_state, long * flagp, unsigned long depth) Line 4811	C
 	miniperl.exe!S_regbranch(RExC_state_t * pRExC_state, long * flagp, long first, unsigned long depth) Line 4576	C
 	miniperl.exe!S_reg(RExC_state_t * pRExC_state, long paren, long * flagp, unsigned long depth) Line 4248	C
 	miniperl.exe!Perl_re_op_compile(sv * * const patternp, int pat_count, op * expr, const regexp_engine * eng, p5rx * old_re, bool * is_bare_re, const unsigned long orig_rx_flags, const unsigned long pm_flags) Line 1793	C
 	miniperl.exe!Perl_pmruntime(op * o, op * expr, op * repl, unsigned __int64 flags, long floor) Line 7765	C
 	miniperl.exe!Perl_bind_match(long type, op * left, op * right) Line 4338	C
 	miniperl.exe!Perl_yyparse(int gramtype) Line 1178	C
 	miniperl.exe!S_doeval_compile(unsigned char gimme, cv * outside, unsigned long seq, hv * hh) Line 4212	C
 	miniperl.exe!S_require_file(sv * sv) Line 5316	C
 	miniperl.exe!Perl_pp_require() Line 5346	C
 	miniperl.exe!Perl_runops_debug() Line 3003	C
 	miniperl.exe!Perl_call_sv(sv * sv, long arg_flags) Line 3342	C
 	miniperl.exe!Perl_call_list(long oldscope, av * paramList) Line 5456	C
 	miniperl.exe!S_process_special_blocks(long floor, const char * const fullname, gv * const gv, cv * const cv) Line 11666	C
 	miniperl.exe!Perl_newATTRSUB_x(long floor, op * o, op * proto, op * attrs, op * block, bool o_is_gv) Line 11500	C
 	miniperl.exe!Perl_utilize(int aver, long floor, op * version, op * idop, op * arg) Line 8267	C
 	miniperl.exe!Perl_yyparse(int gramtype) Line 461	C
 	miniperl.exe!S_parse_body(char * * env, void(*)() xsinit) Line 2779	C
 	miniperl.exe!perl_parse(interpreter * my_perl, void(*)() xsinit, int argc, char * * argv, char * * env) Line 2022	C
 	miniperl.exe!main(int argc, char * * argv, char * * env) Line 112	C
 	[Inline Frame] miniperl.exe!invoke_main() Line 78	C++
 	miniperl.exe!__scrt_common_main_seh() Line 288	C++
 	kernel32.dll!BaseThreadInitThunk�()	Unknown
 	ntdll.dll!RtlUserThreadStart�()	Unknown

Steps to Reproduce
Modify win32_realloc() inside the repo to be similar to this, on Linux this is probably called msize().

DllExport void *
win32_realloc(void *block, size_t size)
{
    //return realloc(block,size);
    if (size == 0) {
        __debugbreak();
        win32_free(block);
        return NULL;
    }
    else if(!block) {
        __debugbreak();
        return win32_malloc(size);
    }
    SIZE_T oldlen = HeapSize(w32_crt_heap, HEAP_GENERATE_EXCEPTIONS, block);
    if (size == oldlen)
        __debugbreak();
    return HeapReAlloc(w32_crt_heap, HEAP_GENERATE_EXCEPTIONS, block, size);
}

then run ..\miniperl.exe -I..\lib -f ..\write_buildcustomize.pl ..

Expected behavior
Don't do redundant realloc()s/Renew***()s/SvPV_shrink_to_cur(sv)s.

If the Perl VM doesn't the length of the memory blocks it obtained from libc, but now wants to shrink the memory blocks from an unknown larger size (hopefully shrinking from larger size during a shrinking operation), but not knowing the current valid length or what is the length of valid data/valid contents of the previously allocated (I hope so) block memory block, ...

That is called heap corruption.

Perl configuration
win64 x64 msvc 2022

#define PERL_REVISION	5		/* age */
#define PERL_VERSION	41		/* epoch */
#define PERL_SUBVERSION	13		/* generation */

update

regcomp.c is spaghetti.

perl5/regcomp.h

Line 476 in 6fbe2c7

#define SIZE_ALIGN NODE_ALIGN

#define NODE_ALIGN(node)
#define SIZE_ALIGN NODE_ALIGN

#undef OP
#undef OPERAND
#undef STRING
#undef NEXT_OFF
#undef NODE_ALIGN

#define NEXT_OFF(p)     ((p)->head.data.next_off)
Search "SIZE_ALIGN" (2 hits in 2 files of 366 searched)
  C:\sources\perl5\regcomp.c (1 hit)
	Line 12849:     SIZE_ALIGN(RExC_size);
  C:\sources\perl5\regcomp.h (1 hit)
	Line  476: #define SIZE_ALIGN NODE_ALIGN
Search "NODE_ALIGN" (7 hits in 2 files of 4806 searched)
  C:\sources\perl5\regcomp.c (1 hit)
	Line 12851:     NODE_ALIGN_FILL(REGNODE_p(ret));
  C:\sources\perl5\regcomp.h (6 hits)
	Line  475: #define NODE_ALIGN(node)
	Line  476: #define SIZE_ALIGN NODE_ALIGN
	Line  482: #undef NODE_ALIGN
	Line  499: #define NODE_ALIGN_FILL(node) (FLAGS(node) = 0)
	Line  541: #undef NODE_ALIGN
	Line  544: #define NODE_ALIGN(node)

Why do these macros even exist when they do nothing?

A quick sloppy git blame shows these macros became unused in recent new code

44eb4cd
Author: Yves Orton
Date: 3/21/2023 5:02:48 PM
Message: regcomp.h - use a common union for head and args across all regnodes.

a very old maybe related commit

830247a
Author: Ilya Zakharevich
Date: 11/17/2000 3:35:11 PM
Message: [PATCH 5.7.0] make regcomp reenterable

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions