diff --git a/cmd/cli/main.go b/cmd/cli/main.go index db77623b..094facea 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -17,7 +17,14 @@ func handleCommands(ctx *cli.Context) error { layout = "plain" } - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: ctx.Bool("proof_mode"), Layout: layout} + proofMode := ctx.Bool("proof_mode") + + secureRun := !proofMode + if ctx.Bool("secure_run") { + secureRun = true + } + + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, ProofMode: proofMode, Layout: layout, SecureRun: secureRun} cairoRunner, err := cairo_run.CairoRun(programPath, cairoRunConfig) if err != nil { @@ -51,6 +58,11 @@ func main() { Aliases: []string{"p"}, Usage: "Run in proof mode", }, + &cli.BoolFlag{ + Name: "secure_run", + Aliases: []string{"s"}, + Usage: "Run security checks. Default: true unless proof_mode is true", + }, &cli.StringFlag{ Name: "layout", Aliases: []string{"l"}, diff --git a/pkg/builtins/bitwise.go b/pkg/builtins/bitwise.go index b2e26a34..39a9a44c 100644 --- a/pkg/builtins/bitwise.go +++ b/pkg/builtins/bitwise.go @@ -111,6 +111,10 @@ func (b *BitwiseBuiltinRunner) CellsPerInstance() uint { return BITWISE_CELLS_PER_INSTANCE } +func (b *BitwiseBuiltinRunner) InputCellsPerInstance() uint { + return BIWISE_INPUT_CELLS_PER_INSTANCE +} + func (b *BitwiseBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -253,3 +257,10 @@ func (r *BitwiseBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMa return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *BitwiseBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/builtin_runner.go b/pkg/builtins/builtin_runner.go index 64148d7f..c9e9c7ad 100644 --- a/pkg/builtins/builtin_runner.go +++ b/pkg/builtins/builtin_runner.go @@ -2,6 +2,7 @@ package builtins import ( "fmt" + "sort" "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" "github.com/pkg/errors" @@ -28,6 +29,10 @@ type BuiltinRunner interface { Base() memory.Relocatable // Returns the name of the builtin Name() string + // Cells per builtin instance + CellsPerInstance() uint + // Input cells per builtin instance + InputCellsPerInstance() uint // Creates a memory segment for the builtin and initializes its base InitializeSegments(*memory.MemorySegmentManager) // Returns the builtin's initial stack @@ -40,27 +45,97 @@ type BuiltinRunner interface { AddValidationRule(*memory.Memory) // Sets the inclusion of the Builtin Runner in the Cairo Runner Include(bool) - // TODO: Later additions -> Some of them could depend on a Default Implementation - // // Most of them depend on Layouts being implemented - // // Use cases: - // // I. PROOF_MODE // Returns the builtin's ratio, is zero if the layout is dynamic Ratio() uint // Returns the builtin's allocated memory units GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) - // // Returns the list of memory addresses used by the builtin + // Returns the list of memory addresses used by the builtin GetMemoryAccesses(*memory.MemorySegmentManager) ([]memory.Relocatable, error) GetRangeCheckUsage(*memory.Memory) (*uint, *uint) GetUsedPermRangeCheckLimits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) GetUsedDilutedCheckUnits(dilutedSpacing uint, dilutedNBits uint) uint GetUsedCellsAndAllocatedSizes(segments *memory.MemorySegmentManager, currentStep uint) (uint, uint, error) FinalStack(segments *memory.MemorySegmentManager, pointer memory.Relocatable) (memory.Relocatable, error) - // // II. SECURITY (secure-run flag cairo-run || verify-secure flag run_from_entrypoint) - // RunSecurityChecks(*vm.VirtualMachine) error // verify_secure_runner logic - // // Returns the base & stop_ptr, stop_ptr can be nil - // GetMemorySegmentAddresses() (memory.Relocatable, *memory.Relocatable) //verify_secure_runner logic - // // III. STARKNET-SPECIFIC + // Returns the base & stop_ptr + GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) + // Amount of builtin instances used GetUsedInstances(*memory.MemorySegmentManager) (uint, error) - // // IV. GENERAL CASE (but not critical) - // FinalStack(*memory.MemorySegmentManager, memory.Relocatable) (memory.Relocatable, error) // read_return_values +} + +func RunSecurityChecksForBuiltin(builtin BuiltinRunner, segments *memory.MemorySegmentManager) error { + if builtin.Name() == OUTPUT_BUILTIN_NAME { + return nil + } + + cellsPerInstance := builtin.CellsPerInstance() + nInputCells := builtin.InputCellsPerInstance() + builtinSegmentIndex := builtin.Base().SegmentIndex + + offsets := make([]int, 0) + // Collect the builtin segment's address' offsets + for addr := range segments.Memory.Data { + if addr.SegmentIndex == builtinSegmentIndex { + offsets = append(offsets, int(addr.Offset)) + } + } + + if len(offsets) == 0 { + // No checks to run for empty segment + return nil + } + // Sort offsets for easier comparison + sort.Ints(offsets) + // Obtain max offset + maxOffset := offsets[len(offsets)-1] + + n := (maxOffset / int(cellsPerInstance)) + 1 + //Verify that n is not too large to make sure the expectedOffsets list that is constructed below is not too large. + if n > len(offsets)/int(nInputCells) { + return errors.Errorf("Missing memory cells for %s", builtin.Name()) + } + + // Check that the two inputs (x and y) of each instance are set. + expectedOffsets := make([]int, 0) + for i := 0; i < n; i++ { + for j := 0; j < int(nInputCells); j++ { + expectedOffsets = append(expectedOffsets, int(cellsPerInstance)*i+j) + } + } + // Find the missing offsets (offsets in expectedOffsets but not in offsets) + missingOffsets := make([]int, 0) + j := 0 + i := 0 + for i < len(expectedOffsets) && j < len(offsets) { + if expectedOffsets[i] < offsets[j] { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + } else { + j++ + } + i++ + } + for i < len(expectedOffsets) { + missingOffsets = append(missingOffsets, expectedOffsets[i]) + i++ + } + if len(missingOffsets) != 0 { + return errors.Errorf("Missing memory cells for builtin: %s: %v", builtin.Name(), missingOffsets) + } + + // Verify auto deduction rules for the unassigned output cells. + // Assigned output cells are checked as part of the call to VerifyAutoDeductions(). + for i := uint(0); i < uint(n); i++ { + for j := uint(nInputCells); j < cellsPerInstance; j++ { + addr := memory.NewRelocatable(builtinSegmentIndex, cellsPerInstance*i+j) + _, err := segments.Memory.Get(addr) + // Output cell not in memory + if err != nil { + _, err = builtin.DeduceMemoryCell(addr, &segments.Memory) + if err != nil { + return err + } + } + } + } + + return nil } diff --git a/pkg/builtins/builtin_runner_test.go b/pkg/builtins/builtin_runner_test.go new file mode 100644 index 00000000..210b25a2 --- /dev/null +++ b/pkg/builtins/builtin_runner_test.go @@ -0,0 +1,105 @@ +package builtins_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestRunSecurityChecksEmptyMemory(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksMissingMemoryCells(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert cells 4-5 and leave the first input cell empty + // This will fail the security checks, as the memory cell with offset 0 will be missing + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(3)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(4)), + } + segments.LoadData(builtinBase.AddUint(1), &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksMissingMemoryCellsNCheck(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // n = max(offsets) // cellsPerInstance + 1 + // n = max[(0]) // 5 + 1 = 0 // 5 + 1 = 1 + // len(offsets) // inputCells = 1 // 2 + // This will fail the security checks, as n > len(offsets) // inputCells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedOk(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(1)), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err != nil { + t.Errorf("RunSecurityChecks failed with error: %s", err.Error()) + } +} + +func TestRunSecurityChecksValidateOutputCellsNotDeducedErr(t *testing.T) { + builtin := builtins.NewBitwiseBuiltinRunner(256) + segments := memory.NewMemorySegmentManager() + + builtin.InitializeSegments(&segments) + builtinBase := builtin.Base() + // A bitwise cell consists of 5 elements: 2 input cells & 3 output cells + // In this test we insert the input cells (1-2), but not the output cells (3-5) + // This will cause the security checks to run the auto-deductions for those output cells + // As we inserted an invalid value (PRIME -1) on the first input cell, this deduction will fail + builtinSegment := []memory.MaybeRelocatable{ + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromDecString("-1")), + *memory.NewMaybeRelocatableFelt(lambdaworks.FeltFromUint(2)), + } + segments.LoadData(builtinBase, &builtinSegment) + + err := builtins.RunSecurityChecksForBuiltin(builtin, &segments) + if err == nil { + t.Errorf("RunSecurityChecks should have failed") + } +} diff --git a/pkg/builtins/ec_op.go b/pkg/builtins/ec_op.go index 9053c25c..912445dd 100644 --- a/pkg/builtins/ec_op.go +++ b/pkg/builtins/ec_op.go @@ -147,6 +147,10 @@ func (r *EcOpBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_EC_OP } +func (b *EcOpBuiltinRunner) InputCellsPerInstance() uint { + return INPUT_CELLS_PER_EC_OP +} + func (ec *EcOpBuiltinRunner) AddValidationRule(*memory.Memory) {} func (ec *EcOpBuiltinRunner) Base() memory.Relocatable { @@ -405,3 +409,10 @@ func (r *EcOpBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentManag return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *EcOpBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/keccak.go b/pkg/builtins/keccak.go index a5f9638e..ac11ae37 100644 --- a/pkg/builtins/keccak.go +++ b/pkg/builtins/keccak.go @@ -539,6 +539,10 @@ func (k *KeccakBuiltinRunner) CellsPerInstance() uint { return KECCAK_CELLS_PER_INSTANCE } +func (b *KeccakBuiltinRunner) InputCellsPerInstance() uint { + return KECCAK_INPUT_CELLS_PER_INSTANCE +} + func (k *KeccakBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -676,3 +680,10 @@ func (r *KeccakBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *KeccakBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/output.go b/pkg/builtins/output.go index 945a772b..f445178b 100644 --- a/pkg/builtins/output.go +++ b/pkg/builtins/output.go @@ -5,6 +5,7 @@ import ( ) const OUTPUT_BUILTIN_NAME = "output" +const OUTPUT_CELLS_PER_INSTANCE = 1 type OutputBuiltinRunner struct { base memory.Relocatable @@ -133,3 +134,18 @@ func (r *OutputBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentMan return usedCells, nil } + +func (b *OutputBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} + +func (r *OutputBuiltinRunner) CellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} + +func (b *OutputBuiltinRunner) InputCellsPerInstance() uint { + return OUTPUT_CELLS_PER_INSTANCE +} diff --git a/pkg/builtins/pedersen.go b/pkg/builtins/pedersen.go index baa1d14c..158ef68e 100644 --- a/pkg/builtins/pedersen.go +++ b/pkg/builtins/pedersen.go @@ -107,6 +107,10 @@ func (p *PedersenBuiltinRunner) CellsPerInstance() uint { return PEDERSEN_CELLS_PER_INSTANCE } +func (p *PedersenBuiltinRunner) InputCellsPerInstance() uint { + return PEDERSEN_INPUT_CELLS_PER_INSTANCE +} + func (p *PedersenBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -228,3 +232,10 @@ func (r *PedersenBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PedersenBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/poseidon.go b/pkg/builtins/poseidon.go index 9bb09e7e..81a9a8e0 100644 --- a/pkg/builtins/poseidon.go +++ b/pkg/builtins/poseidon.go @@ -96,6 +96,10 @@ func (p *PoseidonBuiltinRunner) CellsPerInstance() uint { return POSEIDON_CELLS_PER_INSTANCE } +func (p *PoseidonBuiltinRunner) InputCellsPerInstance() uint { + return POSEIDON_INPUT_CELLS_PER_INSTANCE +} + func (p *PoseidonBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -217,3 +221,10 @@ func (r *PoseidonBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmentM return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *PoseidonBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/range_check.go b/pkg/builtins/range_check.go index 8ee6ae65..ee913b94 100644 --- a/pkg/builtins/range_check.go +++ b/pkg/builtins/range_check.go @@ -110,6 +110,10 @@ func (r *RangeCheckBuiltinRunner) CellsPerInstance() uint { return CELLS_PER_RANGE_CHECK } +func (b *RangeCheckBuiltinRunner) InputCellsPerInstance() uint { + return CELLS_PER_RANGE_CHECK +} + func (r *RangeCheckBuiltinRunner) GetAllocatedMemoryUnits(segments *memory.MemorySegmentManager, currentStep uint) (uint, error) { // This condition corresponds to an uninitialized ratio for the builtin, which should only // happen when layout is `dynamic` @@ -280,3 +284,10 @@ func (r *RangeCheckBuiltinRunner) GetUsedInstances(segments *memory.MemorySegmen return usedCells, nil } + +func (b *RangeCheckBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/builtins/signature.go b/pkg/builtins/signature.go index 5e577b61..ce6865bd 100644 --- a/pkg/builtins/signature.go +++ b/pkg/builtins/signature.go @@ -131,6 +131,10 @@ func (r *SignatureBuiltinRunner) CellsPerInstance() uint { return SIGNATURE_CELLS_PER_INSTANCE } +func (r *SignatureBuiltinRunner) InputCellsPerInstance() uint { + return SIGNATURE_CELLS_PER_INSTANCE +} + func (r *SignatureBuiltinRunner) GetRangeCheckUsage(memory *memory.Memory) (*uint, *uint) { return nil, nil } @@ -235,3 +239,10 @@ func (r *SignatureBuiltinRunner) GetUsedInstances(segments *memory.MemorySegment return utils.DivCeil(usedCells, r.CellsPerInstance()), nil } + +func (b *SignatureBuiltinRunner) GetMemorySegmentAddresses() (memory.Relocatable, memory.Relocatable, error) { + if b.StopPtr == nil { + return memory.Relocatable{}, memory.Relocatable{}, NewErrNoStopPointer(b.Name()) + } + return b.base, memory.NewRelocatable(b.base.SegmentIndex, *b.StopPtr), nil +} diff --git a/pkg/runners/cairo_runner.go b/pkg/runners/cairo_runner.go index 32cb37d4..53354130 100644 --- a/pkg/runners/cairo_runner.go +++ b/pkg/runners/cairo_runner.go @@ -582,14 +582,13 @@ func (runner *CairoRunner) GetExecutionResources() (ExecutionResources, error) { }, nil } -// TODO: Add verifySecure once its implemented /* Runs a cairo program from a give entrypoint, indicated by its pc offset, with the given arguments. If `verifySecure` is set to true, [verifySecureRunner] will be called to run extra verifications. `programSegmentSize` is only used by the [verifySecureRunner] function and will be ignored if `verifySecure` is set to false. Each arg can be either MaybeRelocatable, []MaybeRelocatable or [][]MaybeRelocatable */ -func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor) error { +func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintProcessor vm.HintProcessor, verifySecure bool, programSegmentSize *uint) error { stack := make([]memory.MaybeRelocatable, 0) for _, arg := range args { val, err := runner.Vm.Segments.GenArg(arg) @@ -615,6 +614,8 @@ func (runner *CairoRunner) RunFromEntrypoint(entrypoint uint, args []any, hintPr if err != nil { return err } - // TODO: verifySecureRunner + if verifySecure { + return VerifySecureRunner(runner, true, programSegmentSize) + } return nil } diff --git a/pkg/runners/cairo_runner_test.go b/pkg/runners/cairo_runner_test.go index 1c5b7130..0f5f34c6 100644 --- a/pkg/runners/cairo_runner_test.go +++ b/pkg/runners/cairo_runner_test.go @@ -743,7 +743,7 @@ func TestRunFromEntryPointFibonacci(t *testing.T) { runner.InitializeBuiltins() runner.InitializeSegments() - err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor) + err := runner.RunFromEntrypoint(uint(entrypoint), args, &hintProcessor, true, nil) if err != nil { t.Errorf("Running fib entrypoint failed with error %s", err.Error()) diff --git a/pkg/runners/security.go b/pkg/runners/security.go new file mode 100644 index 00000000..c8c5d4c9 --- /dev/null +++ b/pkg/runners/security.go @@ -0,0 +1,68 @@ +package runners + +import ( + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + "github.com/pkg/errors" +) + +/* +Verify that the completed run in a runner is safe to be relocated and be +used by other Cairo programs. + +Checks include: + - (Only if `verifyBuiltins` is set to true) All accesses to the builtin segments must be within the range defined by + the builtins themselves. + - There must not be accesses to the program segment outside the program + data range. This check will use the `programSegmentSize` instead of the program data length if available. + - All addresses in memory must be real (not temporary) + +Note: Each builtin is responsible for checking its own segments' data. +*/ +func VerifySecureRunner(runner *CairoRunner, verifyBuiltins bool, programSegmentSize *uint) error { + programSize := uint(len(runner.Program.Data)) + if programSegmentSize != nil { + programSize = *programSegmentSize + } + // Get builtin segment info + builtinNames := make(map[int]string) + builtinSizes := make(map[int]uint) + if verifyBuiltins { + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + base, stopPtr, err := runner.Vm.BuiltinRunners[i].GetMemorySegmentAddresses() + if err != nil { + return err + } + builtinNames[base.SegmentIndex] = runner.Vm.BuiltinRunners[i].Name() + builtinSizes[base.SegmentIndex] = stopPtr.Offset + } + } + // Run memory checks + for addr, val := range runner.Vm.Segments.Memory.Data { + // Check out of bound accesses to builtin segment + size, ok := builtinSizes[addr.SegmentIndex] + if ok && addr.Offset >= size { + return errors.Errorf("Out of bounds access to builtin segment %s at %s", builtinNames[addr.SegmentIndex], addr.ToString()) + } + // Check out of bound accesses to program segment + if addr.SegmentIndex == runner.ProgramBase.SegmentIndex && addr.SegmentIndex >= int(programSize) { + return errors.Errorf("Out of bounds access to program segment at %s", addr.ToString()) + } + // Check non-relocated temporary addresses + if addr.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", addr.ToString()) + } + relVal, isRel := val.GetRelocatable() + if isRel && relVal.SegmentIndex < 0 { + return errors.Errorf("Security Error: Invalid Memory Value: temporary address not relocated: %s", relVal.ToString()) + } + } + // Run builtin-specific checks + for i := 0; i < len(runner.Vm.BuiltinRunners); i++ { + err := builtins.RunSecurityChecksForBuiltin(runner.Vm.BuiltinRunners[i], &runner.Vm.Segments) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/runners/security_test.go b/pkg/runners/security_test.go new file mode 100644 index 00000000..dd808ac7 --- /dev/null +++ b/pkg/runners/security_test.go @@ -0,0 +1,55 @@ +package runners_test + +import ( + "testing" + + "github.com/lambdaclass/cairo-vm.go/pkg/builtins" + . "github.com/lambdaclass/cairo-vm.go/pkg/lambdaworks" + . "github.com/lambdaclass/cairo-vm.go/pkg/runners" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm" + . "github.com/lambdaclass/cairo-vm.go/pkg/vm/memory" +) + +func TestVerifySecureRunnerEmptyMemory(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + err := VerifySecureRunner(runner, true, nil) + if err != nil { + t.Errorf("VerifySecureRunner failed with error: %s", err.Error()) + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessProgram(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert an element into the program segment to trigger out of bounds access to program segment error + runner.Vm.Segments.Memory.Insert(runner.ProgramBase, NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerOutOfBoundsAccessBuiltin(t *testing.T) { + runner, _ := NewCairoRunner(Program{Builtins: []string{builtins.OUTPUT_BUILTIN_NAME}}, "all_cairo", false) + runner.Initialize() + stopPtr := uint(0) + runner.Vm.BuiltinRunners[0].(*builtins.OutputBuiltinRunner).StopPtr = &stopPtr + // Insert an element into the output segment to trigger out of bounds access to builtin segment error + runner.Vm.Segments.Memory.Insert(runner.Vm.BuiltinRunners[0].Base(), NewMaybeRelocatableFelt(FeltOne())) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} + +func TestVerifySecureRunnerTemporaryVal(t *testing.T) { + runner, _ := NewCairoRunner(Program{}, "all_cairo", false) + runner.Initialize() + // Insert a temporary address into memory + runner.Vm.Segments.Memory.Insert(NewRelocatable(1, 7), NewMaybeRelocatableRelocatable(NewRelocatable(-1, 0))) + err := VerifySecureRunner(runner, true, nil) + if err == nil { + t.Errorf("VerifySecureRunner should have failed") + } +} diff --git a/pkg/vm/cairo_run/cairo_run.go b/pkg/vm/cairo_run/cairo_run.go index d54a1f99..db6b568a 100644 --- a/pkg/vm/cairo_run/cairo_run.go +++ b/pkg/vm/cairo_run/cairo_run.go @@ -24,6 +24,7 @@ type CairoRunConfig struct { DisableTracePadding bool ProofMode bool Layout string + SecureRun bool } func CairoRunError(err error) error { @@ -67,6 +68,13 @@ func CairoRun(programPath string, cairoRunConfig CairoRunConfig) (*runners.Cairo cairoRunner.FinalizeSegments() } + if cairoRunConfig.SecureRun { + err = runners.VerifySecureRunner(cairoRunner, true, nil) + if err != nil { + return nil, err + } + } + err = cairoRunner.Vm.Relocate() return cairoRunner, err } diff --git a/pkg/vm/cairo_run/cairo_run_test.go b/pkg/vm/cairo_run/cairo_run_test.go index 761e4e80..19d3a08e 100644 --- a/pkg/vm/cairo_run/cairo_run_test.go +++ b/pkg/vm/cairo_run/cairo_run_test.go @@ -8,7 +8,7 @@ import ( ) func testProgram(programName string, t *testing.T) { - cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false} + cairoRunConfig := cairo_run.CairoRunConfig{DisableTracePadding: false, Layout: "all_cairo", ProofMode: false, SecureRun: true} _, err := cairo_run.CairoRun("../../../cairo_programs/"+programName+".json", cairoRunConfig) if err != nil { t.Errorf("Program execution failed with error: %s", err)