/usr/share/pyshared/RestrictedPython/tests/verify.py is in python-restrictedpython 3.6.0-0ubuntu4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | ##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Verify simple properties of bytecode.
Some of the transformations performed by the RestrictionMutator are
tricky. This module checks the generated bytecode as a way to verify
the correctness of the transformations. Violations of some
restrictions are obvious from inspection of the bytecode. For
example, the bytecode should never contain a LOAD_ATTR call, because
all attribute access is performed via the _getattr_() checker
function.
"""
import dis
import types
def verify(code):
"""Verify all code objects reachable from code.
In particular, traverse into contained code objects in the
co_consts table.
"""
verifycode(code)
for ob in code.co_consts:
if isinstance(ob, types.CodeType):
verify(ob)
def verifycode(code):
try:
_verifycode(code)
except:
dis.dis(code)
raise
def _verifycode(code):
line = code.co_firstlineno
# keep a window of the last three opcodes, with the most recent first
window = (None, None, None)
with_context = (None, None)
for op in disassemble(code):
if op.line is not None:
line = op.line
if op.opname.endswith("LOAD_ATTR"):
# All the user code that generates LOAD_ATTR should be
# rewritten, but the code generated for a list comp
# includes a LOAD_ATTR to extract the append method.
# Another exception is the new-in-Python 2.6 'context
# managers', which do a LOAD_ATTR for __exit__ and
# __enter__.
if op.arg == "__exit__":
with_context = (op, with_context[1])
elif op.arg == "__enter__":
with_context = (with_context[0], op)
elif not ((op.arg == "__enter__" and
window[0].opname == "ROT_TWO" and
window[1].opname == "DUP_TOP") or
(op.arg == "append" and
window[0].opname == "DUP_TOP" and
window[1].opname == "BUILD_LIST")):
raise ValueError("direct attribute access %s: %s, %s:%d"
% (op.opname, op.arg, code.co_filename, line))
if op.opname in ("WITH_CLEANUP"):
# Here we check if the LOAD_ATTR for __exit__ and
# __enter__ were part of a 'with' statement by checking
# for the 'WITH_CLEANUP' bytecode. If one is seen, we
# clear the with_context variable and let it go. The
# access was safe.
with_context = (None, None)
if op.opname in ("STORE_ATTR", "DEL_ATTR"):
if not (window[0].opname == "CALL_FUNCTION" and
window[2].opname == "LOAD_GLOBAL" and
window[2].arg == "_write_"):
# check that arg is appropriately wrapped
for i, op in enumerate(window):
print i, op.opname, op.arg
raise ValueError("unguard attribute set/del at %s:%d"
% (code.co_filename, line))
if op.opname.startswith("UNPACK"):
# An UNPACK opcode extracts items from iterables, and that's
# unsafe. The restricted compiler doesn't remove UNPACK opcodes,
# but rather *inserts* a call to _getiter_() before each, and
# that's the pattern we need to see.
if not (window[0].opname == "CALL_FUNCTION" and
window[1].opname == "ROT_TWO" and
window[2].opname == "LOAD_GLOBAL" and
window[2].arg == "_getiter_"):
raise ValueError("unguarded unpack sequence at %s:%d" %
(code.co_filename, line))
# should check CALL_FUNCTION_{VAR,KW,VAR_KW} but that would
# require a potentially unlimited history. need to refactor
# the "window" before I can do that.
if op.opname == "LOAD_SUBSCR":
raise ValueError("unguarded index of sequence at %s:%d" %
(code.co_filename, line))
window = (op,) + window[:2]
if not with_context == (None, None):
# An access to __enter__ and __exit__ was performed but not as
# part of a 'with' statement. This is not allowed.
for op in with_context:
if op is not None:
if op.line is not None:
line = op.line
raise ValueError("direct attribute access %s: %s, %s:%d"
% (op.opname, op.arg, code.co_filename, line))
class Op(object):
__slots__ = (
"opname", # string, name of the opcode
"argcode", # int, the number of the argument
"arg", # any, the object, name, or value of argcode
"line", # int, line number or None
"target", # boolean, is this op the target of a jump
"pos", # int, offset in the bytecode
)
def __init__(self, opcode, pos):
self.opname = dis.opname[opcode]
self.arg = None
self.line = None
self.target = False
self.pos = pos
def disassemble(co, lasti=-1):
code = co.co_code
labels = dis.findlabels(code)
linestarts = dict(findlinestarts(co))
n = len(code)
i = 0
extended_arg = 0
free = co.co_cellvars + co.co_freevars
while i < n:
op = ord(code[i])
o = Op(op, i)
i += 1
if i in linestarts and i > 0:
o.line = linestarts[i]
if i in labels:
o.target = True
if op > dis.HAVE_ARGUMENT:
arg = ord(code[i]) + ord(code[i+1]) * 256 + extended_arg
extended_arg = 0
i += 2
if op == dis.EXTENDED_ARG:
extended_arg = arg << 16
o.argcode = arg
if op in dis.hasconst:
o.arg = co.co_consts[arg]
elif op in dis.hasname:
o.arg = co.co_names[arg]
elif op in dis.hasjrel:
o.arg = i + arg
elif op in dis.haslocal:
o.arg = co.co_varnames[arg]
elif op in dis.hascompare:
o.arg = dis.cmp_op[arg]
elif op in dis.hasfree:
o.arg = free[arg]
yield o
# findlinestarts is copied from Python 2.4's dis module. The code
# didn't exist in 2.3, but it would be painful to code disassemble()
# without it.
def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.
Generate pairs (offset, lineno) as described in Python/compile.c.
"""
byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
line_increments = [ord(c) for c in code.co_lnotab[1::2]]
lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)
|