#if defined(PL_OP_SLAB_ALLOC)
Apa |void* |Slab_Alloc |int m|size_t sz
Ap |void |Slab_Free |NN void *op
+# if defined(PERL_DEBUG_READONLY_OPS)
+poxM |void |pending_Slabs_to_ro
+# if defined(PERL_IN_OP_C)
+s |void |Slab_to_rw |NN void *op
+# endif
+# endif
#endif
#if defined(PERL_IN_PERL_C) || defined(PERL_DECL_PROT)
#if defined(PL_OP_SLAB_ALLOC)
#define Slab_Alloc Perl_Slab_Alloc
#define Slab_Free Perl_Slab_Free
+# if defined(PERL_DEBUG_READONLY_OPS)
+# if defined(PERL_IN_OP_C)
+#ifdef PERL_CORE
+#define Slab_to_rw S_Slab_to_rw
+#endif
+# endif
+# endif
#endif
#if defined(PERL_IN_PERL_C) || defined(PERL_DECL_PROT)
#ifdef PERL_CORE
#if defined(PL_OP_SLAB_ALLOC)
#define Slab_Alloc(a,b) Perl_Slab_Alloc(aTHX_ a,b)
#define Slab_Free(a) Perl_Slab_Free(aTHX_ a)
+# if defined(PERL_DEBUG_READONLY_OPS)
+#ifdef PERL_CORE
+#endif
+# if defined(PERL_IN_OP_C)
+#ifdef PERL_CORE
+#define Slab_to_rw(a) S_Slab_to_rw(aTHX_ a)
+#endif
+# endif
+# endif
#endif
#if defined(PERL_IN_PERL_C) || defined(PERL_DECL_PROT)
#ifdef PERL_CORE
#define PL_sig_pending (vTHX->Isig_pending)
#define PL_sighandlerp (vTHX->Isighandlerp)
#define PL_signals (vTHX->Isignals)
+#define PL_slab_count (vTHX->Islab_count)
+#define PL_slabs (vTHX->Islabs)
#define PL_sort_RealCmp (vTHX->Isort_RealCmp)
#define PL_splitstr (vTHX->Isplitstr)
#define PL_srand_called (vTHX->Isrand_called)
#define PL_Isig_pending PL_sig_pending
#define PL_Isighandlerp PL_sighandlerp
#define PL_Isignals PL_signals
+#define PL_Islab_count PL_slab_count
+#define PL_Islabs PL_slabs
#define PL_Isort_RealCmp PL_sort_RealCmp
#define PL_Isplitstr PL_splitstr
#define PL_Isrand_called PL_srand_called
PERLVARI(Iutf8cache, I8, 1) /* Is the utf8 caching code enabled? */
#endif
+#ifdef PERL_DEBUG_READONLY_OPS
+PERLVARI(Islabs, I32**, NULL) /* Array of slabs that have been allocated */
+PERLVARI(Islab_count, U32, 0) /* Size of the array */
+#endif
+
/* New variables must be added to the very end, before this comment,
* for binary compatibility (the offsets of the old members must not change).
* (Don't forget to add your variable also to perl_clone()!)
#if defined(PL_OP_SLAB_ALLOC)
+#ifdef PERL_DEBUG_READONLY_OPS
+# define PERL_SLAB_SIZE 4096
+# include <sys/mman.h>
+#endif
+
#ifndef PERL_SLAB_SIZE
#define PERL_SLAB_SIZE 2048
#endif
*/
sz = (sz + 2*sizeof(I32 *) -1)/sizeof(I32 *);
if ((PL_OpSpace -= sz) < 0) {
+#ifdef PERL_DEBUG_READONLY_OPS
+ /* We need to allocate chunk by chunk so that we can control the VM
+ mapping */
+ PL_OpPtr = mmap(0, PERL_SLAB_SIZE*sizeof(I32*), PROT_READ|PROT_WRITE,
+ MAP_ANON|MAP_PRIVATE, -1, 0);
+
+ DEBUG_m(PerlIO_printf(Perl_debug_log, "mapped %lu at %p\n",
+ (unsigned long) PERL_SLAB_SIZE*sizeof(I32*),
+ PL_OpPtr));
+ if(PL_OpPtr == MAP_FAILED) {
+ perror("mmap failed");
+ abort();
+ }
+#else
PL_OpPtr = (I32 **) PerlMemShared_malloc(PERL_SLAB_SIZE*sizeof(I32*));
+#endif
if (!PL_OpPtr) {
return NULL;
}
means that at run time access is cache friendly upward
*/
PL_OpPtr += PERL_SLAB_SIZE;
+
+#ifdef PERL_DEBUG_READONLY_OPS
+ /* We remember this slab. */
+ /* This implementation isn't efficient, but it is simple. */
+ PL_slabs = realloc(PL_slabs, sizeof(I32**) * (PL_slab_count + 1));
+ PL_slabs[PL_slab_count++] = PL_OpSlab;
+ DEBUG_m(PerlIO_printf(Perl_debug_log, "Allocate %p\n", PL_OpSlab));
+#endif
}
assert( PL_OpSpace >= 0 );
/* Move the allocation pointer down */
return (void *)(PL_OpPtr + 1);
}
+#ifdef PERL_DEBUG_READONLY_OPS
+void
+Perl_pending_Slabs_to_ro(pTHX) {
+ /* Turn all the allocated op slabs read only. */
+ U32 count = PL_slab_count;
+ I32 **const slabs = PL_slabs;
+
+ /* Reset the array of pending OP slabs, as we're about to turn this lot
+ read only. Also, do it ahead of the loop in case the warn triggers,
+ and a warn handler has an eval */
+
+ free(PL_slabs);
+ PL_slabs = NULL;
+ PL_slab_count = 0;
+
+ /* Force a new slab for any further allocation. */
+ PL_OpSpace = 0;
+
+ while (count--) {
+ const void *start = slabs[count];
+ const size_t size = PERL_SLAB_SIZE* sizeof(I32*);
+ if(mprotect(start, size, PROT_READ)) {
+ Perl_warn(aTHX_ "mprotect for %p %lu failed with %d",
+ start, (unsigned long) size, errno);
+ }
+ }
+}
+
+STATIC void
+S_Slab_to_rw(pTHX_ void *op)
+{
+ I32 * const * const ptr = (I32 **) op;
+ I32 * const slab = ptr[-1];
+ assert( ptr-1 > (I32 **) slab );
+ assert( ptr < ( (I32 **) slab + PERL_SLAB_SIZE) );
+ assert( *slab > 0 );
+ if(mprotect(slab, PERL_SLAB_SIZE*sizeof(I32*), PROT_READ|PROT_WRITE)) {
+ Perl_warn(aTHX_ "mprotect RW for %p %lu failed with %d",
+ slab, (unsigned long) PERL_SLAB_SIZE*sizeof(I32*), errno);
+ }
+}
+#else
+# define Slab_to_rw(op)
+#endif
+
void
Perl_Slab_Free(pTHX_ void *op)
{
assert( ptr-1 > (I32 **) slab );
assert( ptr < ( (I32 **) slab + PERL_SLAB_SIZE) );
assert( *slab > 0 );
+ Slab_to_rw(op);
if (--(*slab) == 0) {
# ifdef NETWARE
# define PerlMemShared PerlMem
# endif
+#ifdef PERL_DEBUG_READONLY_OPS
+ /* Need to remove this slab from our list of slabs */
+ {
+ U32 count = PL_slab_count;
+
+ while (count--) {
+ if (PL_slabs[count] == slab) {
+ /* Found it. Move the entry at the end to overwrite it. */
+ DEBUG_m(PerlIO_printf(Perl_debug_log,
+ "Deallocate %p by moving %p from %lu to %lu\n",
+ PL_OpSlab,
+ PL_slabs[PL_slab_count - 1],
+ PL_slab_count, count));
+ PL_slabs[count] = PL_slabs[--PL_slab_count];
+ /* Could realloc smaller at this point, but probably not
+ worth it. */
+ goto gotcha;
+ }
+
+ }
+ Perl_croak(aTHX_
+ "panic: Couldn't find slab at %p (%lu allocated)",
+ slab, (unsigned long) PL_slabs);
+ gotcha:
+ if(munmap(slab, PERL_SLAB_SIZE*sizeof(I32*))) {
+ perror("munmap failed");
+ abort();
+ }
+ }
+#else
PerlMemShared_free(slab);
+#endif
if (slab == PL_OpSlab) {
PL_OpSpace = 0;
}
case OP_LEAVEWRITE:
{
PADOFFSET refcnt;
+#ifdef PERL_DEBUG_READONLY_OPS
+ Slab_to_rw(o);
+#endif
OP_REFCNT_LOCK;
refcnt = OpREFCNT_dec(o);
OP_REFCNT_UNLOCK;
#endif
PL_sv_count = 0;
+#ifdef PERL_DEBUG_READONLY_OPS
+ free(PL_slabs);
+ PL_slabs = NULL;
+ PL_slab_count = 0;
+#endif
#if defined(PERLIO_LAYERS)
/* No more IO - including error messages ! */
return ret;
}
+#ifdef PERL_DEBUG_READONLY_OPS
+# include <sys/mman.h>
+#endif
STATIC void
S_run_body(pTHX_ I32 oldscope)
sv_setiv(PL_DBsingle, 1);
if (PL_initav)
call_list(oldscope, PL_initav);
+#ifdef PERL_DEBUG_READONLY_OPS
+ Perl_pending_Slabs_to_ro(aTHX);
+#endif
}
/* do it */
#define PL_sighandlerp (*Perl_Isighandlerp_ptr(aTHX))
#undef PL_signals
#define PL_signals (*Perl_Isignals_ptr(aTHX))
+#undef PL_slab_count
+#define PL_slab_count (*Perl_Islab_count_ptr(aTHX))
+#undef PL_slabs
+#define PL_slabs (*Perl_Islabs_ptr(aTHX))
#undef PL_sort_RealCmp
#define PL_sort_RealCmp (*Perl_Isort_RealCmp_ptr(aTHX))
#undef PL_splitstr
or 0xEFEFEFEF, you may be seeing the effect of the Poison() macros,
see L<perlclib>.
+=item *
+
+Under ithreads the optree is read only. If you want to enforce this, to check
+for write accesses from buggy code, compile with C<-DPL_OP_SLAB_ALLOC> to
+enable the OP slab allocator and C<-DPERL_DEBUG_READONLY_OPS> to enable code
+that allocates op memory via C<mmap>, and sets it read-only at run time.
+Any write access to an op results in a C<SIGBUS> and abort.
+
+This code is intended for development only, and may not be portable even to
+all Unix variants. Also, it is an 80% solution, in that it isn't able to make
+all ops read only. Specifically it
+
+=over
+
+=item 1
+
+Only sets read-only on all slabs of ops at C<CHECK> time, hence ops allocated
+later via C<require> or C<eval> will be re-write
+
+=item 2
+
+Turns an entire slab of ops read-write if the refcount of any op in the slab
+needs to be decreased.
+
+=item 3
+
+Turns an entire slab of ops read-write if any op from the slab is freed.
+
=back
+It's not possible to turn the slabs to read-only after an action requiring
+read-write access, as either can happen during op tree building time, so
+there may still be legitimate write access.
+
+However, as an 80% solution it is still effective, as currently it catches
+a write access during the generation of F<Config.pm>, which means that we
+can't yet build F<perl> with this enabled.
+
+=back
+
+
=head1 CONCLUSION
We've had a brief look around the Perl source, how to maintain quality
PERL_CALLCONV void Perl_Slab_Free(pTHX_ void *op)
__attribute__nonnull__(pTHX_1);
+# if defined(PERL_DEBUG_READONLY_OPS)
+PERL_CALLCONV void Perl_pending_Slabs_to_ro(pTHX);
+# if defined(PERL_IN_OP_C)
+STATIC void S_Slab_to_rw(pTHX_ void *op)
+ __attribute__nonnull__(pTHX_1);
+
+# endif
+# endif
#endif
#if defined(PERL_IN_PERL_C) || defined(PERL_DECL_PROT)