From d606c271f98426d47ffe28ed47a7104685c9222b Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 26 Nov 2024 15:56:41 +0100 Subject: [PATCH 001/104] Add MWE for proving SIF --- verification/scratch/mwe1.gobra | 244 ++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 verification/scratch/mwe1.gobra diff --git a/verification/scratch/mwe1.gobra b/verification/scratch/mwe1.gobra new file mode 100644 index 000000000..f3c572ad8 --- /dev/null +++ b/verification/scratch/mwe1.gobra @@ -0,0 +1,244 @@ +// Minimal working example 1. Only implements very basic structure. +// 0. The key K is already given. +// 1. We receive a message M over the network. +// 2. We compute a "MAC tag" of M using K. +// 3. We send the computed tag over the network. +package mwe + +////////////////////////////// I/O Actions ////////////////////////////// +// cf. running-example/policy/actions.gobra + +// Definition of I/O actions (abstract, for trace). +type Action adt { + RecvIO{int} + SendIO{int} + // NOTE: I have left out `p` (cf. paper) for now + // It would be necessary once we have multiple options of which message to + // declassify at some point in order to make this deterministic in the + // previous I/O actions again. However, at the moment, our IOD spec only + // allows declassifying one message depending on the previous I/O actions + DeclassifyIO{int} +} + +// Extract input of I/O action. 0 signifies no input. +ghost +decreases +pure func (a Action) Input() int { + return match a { + case RecvIO{?m}: m + case SendIO{_}: 0 + case DeclassifyIO{_}: 0 + } +} + +// Extract output of I/O action. 0 signifies no output. +ghost +decreases +pure func (a Action) Output() int { + return match a { + case RecvIO{_}: 0 + case SendIO{?t}: t + case DeclassifyIO{?t}: t + } +} + +////////////////////////////// Classification spec. ////////////////////////////// +// cf. running-example/policy/classification.gobra +// cf. running-example/classifications/basic.gobra + +type ClassificationSpec interface { + ghost + decreases + pure Classification(Action) Specification +} + +// Gives universal access to the trace. +// `pure` ensures the resulting pointer always points to the same trace. +ghost +decreases +pure func History() *Trace + +type Trace adt { + Empty{} + Snoc{Trace;Action} // Snoc: reverse cons +} + +type Specification adt { + Spec{Observation;Observation} +} + +type Observation adt { + Value{int} // NOTE: eventually might want to switch back to any + None{} + Some{Observation} + Tuple{Observation;Observation} +} + +type ObservationTrace adt { + EmptyObs{} + SnocObs{InitObs ObservationTrace;Observation} +} + +// The following is our assertion language. +ghost +decreases +pure func True() Observation { + return None{} +} + +ghost +decreases +pure func Low(v int) Observation { + return Value{v} +} + +// Given that all sensitivity preconditions have been satisfied in the trace, +// this allows us to assume that the sensitivity postconditions are satisfied. +ghost +decreases +requires acc(History(), 1/2) && low(Pre(sig,*History())) +ensures acc(History(), 1/2) && low(Post(sig,*History())) +func LowPost(sig ClassificationSpec) + +type Collect domain { + func Post(ClassificationSpec, Trace) ObservationTrace + func Pre(ClassificationSpec, Trace) ObservationTrace + func pre_(Specification) Observation + func post_(Specification) Observation + + // NOTE: these are the low projections mentioned in the paper + axiom { + (forall p,q Observation :: {pre_(Spec{p,q})} pre_(Spec{p,q}) == p) && + (forall p,q Observation :: {post_(Spec{p,q})} post_(Spec{p,q}) == q) + } + + axiom { + (forall sig ClassificationSpec :: {Post(sig,Empty{})} Post(sig,Empty{}) == EmptyObs{}) && + (forall sig ClassificationSpec :: {Pre(sig,Empty{})} Pre(sig,Empty{}) == EmptyObs{}) && + (forall sig ClassificationSpec, t Trace, e Action :: {Post(sig,Snoc{t,e})} Post(sig,Snoc{t,e}) == SnocObs{Post(sig,t), post_(sig.Classification(e))}) && + (forall sig ClassificationSpec, t Trace, e Action :: {Pre(sig,Snoc{t,e})} Pre(sig,Snoc{t,e}) == SnocObs{Pre(sig,t), pre_(sig.Classification(e))}) + } +} + +type DefaultClassification struct {} + +ghost +decreases +pure func (DefaultClassification) Classification(a Action) Specification { + return match a { + case DeclassifyIO{?t}: Spec{True(), Low(t)} // Make `t` low. + case _: Spec{Low(a.Output()), Low(a.Input())} + } +} + +////////////////////////////// I/O spec. ////////////////////////////// +// cf. running-example/policy/iodspec.gobra + +// We express the IODSpec as a (IOD-)guarded transition system. +type IODSpec interface { + // `Guard` specifies which I/O actions may be taken, depending on the + // (content of) the action (in particular, not on the sensitivity). + ghost + decreases + pure Guard(state, Action) bool + + ghost + decreases + pure Update(state, Action) state +} + +type Restriction domain { + func IsTrace(IODSpec, Trace) bool + func Reaches(IODSpec, Trace, state) bool + + axiom { + forall r IODSpec, t Trace, s state, a Action :: { Snoc{t, a}, Reaches(r, t, s) } Reaches(r, t, s) && r.Guard(s, a) ==> Reaches(r, Snoc{t, a}, r.Update(s, a)) + } + + axiom { + forall r IODSpec, t Trace, s state :: {Reaches(r, t, s)} Reaches(r, t, s) ==> IsTrace(r,t) + } +} + +// Our I/O spec. The state is the private key and the most recently received message. +type MWE1 struct {} + +type state struct { + key int + lastMsg int +} + +// We allow send, recv to happen at any point. +// Declassify can only be called on a MAC tag of the most recently received message +// generated with the private key. +ghost +decreases +pure func (MWE1) Guard(s state, a Action) bool { + return match a { + // NOTE: This makess our IOD spec well-formed, as what is allowed to be + // declassified is now deterministic in the previous I/O actions. + case DeclassifyIO{?t}: t == MAC(s.key, s.lastMsg) + case _: true + } +} + +ghost +decreases +pure func (MWE1) Update(s state, a Action) state { + return match a { + case RecvIO{?m}: state { key: s.key, lastMsg: m } + case _: s + } +} + +////////////////////////////// Trusted library I/O ////////////////////////////// +// cf. running-example/library/library.gobra + +// Receive message `m` over network. +decreases +requires acc(History()) +ensures acc(History()) && *History() == Snoc{old(*History()), RecvIO{m}} +func Recv() (m int) + +// Send tag `t` over network. +decreases +requires acc(History()) +ensures acc(History()) && *History() == Snoc{old(*History()), SendIO{t}} +func Send(t int) + +// Declassify tag `t`. +ghost +decreases +requires acc(History()) +ensures acc(History()) && *History() == Snoc{old(*History()), DeclassifyIO{t}} +func Declassify(t int) + + +////////////////////////////// Program ////////////////////////////// + +// Abstract function representing the computation of a MAC. +// key x message -> MAC tag +decreases +pure func MAC(int, int) int + +// Receives a message, authenticates it using a MAC, and sends the resulting tag. +// The state `s` contains the private key of this router, +// and the most recently received message. +// NOTE: it should suffice here to just require Reaches(...) after the program +// has terminated, bc. at the moment we definitely terminate and there is no way +// to violate the I/O spec. and "undo" this violation later on (-> safety property). +// TODO: in the future, we should probably check this after every I/O action instead? +// In the original example, I think this is done via the shared invariant. +preserves acc(History()) && acc(s) && low(Pre(DefaultClassification{}, *History())) && Reaches(MWE1{}, *History(), *s) +func authenticate(ghost s *state) { + m := Recv() + LowPost(DefaultClassification{}) + ghost s.lastMsg = m + + t := MAC(s.key, m) + + /* ghost */ Declassify(t) + /* ghost */ LowPost(DefaultClassification{}) + Send(t) + /* ghost */ LowPost(DefaultClassification{}) +} \ No newline at end of file From f5603b6a76422924712c447d7f01758876cacd6e Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 9 Dec 2024 11:17:01 +0100 Subject: [PATCH 002/104] Add MWE2 --- verification/scratch/{ => mwe1}/mwe1.gobra | 2 +- verification/scratch/mwe2/mwe2.gobra | 132 +++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) rename verification/scratch/{ => mwe1}/mwe1.gobra (99%) create mode 100644 verification/scratch/mwe2/mwe2.gobra diff --git a/verification/scratch/mwe1.gobra b/verification/scratch/mwe1/mwe1.gobra similarity index 99% rename from verification/scratch/mwe1.gobra rename to verification/scratch/mwe1/mwe1.gobra index f3c572ad8..4b8e49984 100644 --- a/verification/scratch/mwe1.gobra +++ b/verification/scratch/mwe1/mwe1.gobra @@ -3,7 +3,7 @@ // 1. We receive a message M over the network. // 2. We compute a "MAC tag" of M using K. // 3. We send the computed tag over the network. -package mwe +package mwe1 ////////////////////////////// I/O Actions ////////////////////////////// // cf. running-example/policy/actions.gobra diff --git a/verification/scratch/mwe2/mwe2.gobra b/verification/scratch/mwe2/mwe2.gobra new file mode 100644 index 000000000..8f1a1297f --- /dev/null +++ b/verification/scratch/mwe2/mwe2.gobra @@ -0,0 +1,132 @@ +// Minimal working example 2. +package mwe2 + +// Used to track position in protocol. +type Place int +pred token(ghost p Place) + +pred RecvPerm(ghost p Place) + +// Returns the next place after calling `Recv` from `p`. +// Avoids using an existential quantifier. +ghost +requires RecvPerm(p) +decreases +pure func Recv_T(ghost p Place) Place + +// Used to refer to the received message. +ghost +requires RecvPerm(p) +decreases +pure func Recv_R(ghost p Place) int + +// This is how the state is/should be updated after receiving message `m`. +decreases +pure func Recv_S(s state, m int) state { + return state { key: s.key, lastMsg1: m, lastMsg2: s.lastMsg1, lastMsg3: s.lastMsg2 } +} + +requires token(p) && RecvPerm(p) +ensures token(old(Recv_T(p))) +ensures m == old(Recv_R(p)) +ensures low(m) +func Recv(ghost p Place) (m int) + +pred SendPerm(ghost p Place, t int) + +ghost +requires SendPerm(p, t) +decreases +pure func Send_T(ghost p Place, t int) Place + +requires token(p) && SendPerm(p, t) +requires low(t) +ensures token(old(Send_T(p, t))) +func Send(ghost p Place, t int) + +pred DeclassifyPerm(ghost p Place, tag int, t int) + +ghost +requires DeclassifyPerm(p, tag, t) +decreases +pure func Declassify_T(ghost p Place, tag int, t int) Place + +// In order to make permitted declassifications deterministic in low data, +// we add low parameter `p` to "tag" declassifications. +// (Necessary as otherwise IOD spec Protocol2 would not be well-formed.) +ghost +requires token(p) && DeclassifyPerm(p, tag, t) +requires low(tag) +ensures token(old(Declassify_T(p, tag, t))) +ensures low(t) +decreases +func Declassify(ghost p Place, tag int, t int) + +// "Linear" protocol. +pred Protocol1(ghost p Place, key int) { + // 1. Receive a message. + RecvPerm(p) && + // 2. Compute MAC tag and declassify it. + DeclassifyPerm(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))) && + // 3. Send MAC tag over network. + SendPerm(Declassify_T(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))), MAC(key, Recv_R(p))) && + // 4. Restart. + Protocol1(Send_T(Declassify_T(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))), MAC(key, Recv_R(p))), key) +} + +type state struct { + key int // the private key + lastMsg1 int // 1st most recently received message + lastMsg2 int // 2nd + lastMsg3 int // 3rd +} + +pred Protocol2(ghost p Place, s state) { + // Receive a message at any time. + RecvPerm(p) && Protocol2(Recv_T(p), Recv_S(s, Recv_R(p))) && + // NOTE: at the moment we can declassify things before receiving anything + // Declassify and send either the most or the 2nd most recently received message. + DeclassifyPerm(p, s.lastMsg1, MAC(s.key, s.lastMsg1)) && Protocol2(Declassify_T(p, s.lastMsg1, MAC(s.key, s.lastMsg1)), s) && + DeclassifyPerm(p, s.lastMsg2, MAC(s.key, s.lastMsg2)) && Protocol2(Declassify_T(p, s.lastMsg2, MAC(s.key, s.lastMsg2)), s) && + SendPerm(p, MAC(s.key, s.lastMsg1)) && Protocol2(Send_T(p, MAC(s.key, s.lastMsg1)), s) && + SendPerm(p, MAC(s.key, s.lastMsg2)) && Protocol2(Send_T(p, MAC(s.key, s.lastMsg2)), s) +} + +// Abstract function representing the computation of a MAC. +// key x message -> MAC tag +decreases +pure func MAC(int, int) int + +requires token(p0) && Protocol2(p0, s) +func authenticate(ghost p0 Place, s state) { + + invariant token(p0) && Protocol2(p0, s) + for { + unfold Protocol2(p0, s) + ghost p1 := Recv_T(p0) + m1 := Recv(p0) + s = Recv_S(s, m1) + + unfold Protocol2(p1, s) + ghost p2 := Recv_T(p1) + m2 := Recv(p1) + s = Recv_S(s, m2) + + unfold Protocol2(p2, s) + ghost p3 := Recv_T(p2) + m3 := Recv(p2) + s = Recv_S(s, m3) + + // We can use m2, m3 here. m1 won't work. + t := MAC(s.key, m3) + + unfold Protocol2(p3, s) + ghost p4 := Declassify_T(p3, m3, t) + Declassify(p3, m3, t) + + unfold Protocol2(p4, s) + ghost p0 = Send_T(p4, t) + Send(p4, t) + } + +} \ No newline at end of file From 9328a953930c68731548e6ca680151041e09953b Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 17 Dec 2024 14:55:43 +0100 Subject: [PATCH 003/104] Address PR feedback --- verification/scratch/mwe1/mwe1.gobra | 67 +++++++++++++++-------- verification/scratch/mwe2/mwe2.gobra | 79 +++++++++++++++------------- 2 files changed, 86 insertions(+), 60 deletions(-) diff --git a/verification/scratch/mwe1/mwe1.gobra b/verification/scratch/mwe1/mwe1.gobra index 4b8e49984..97d747383 100644 --- a/verification/scratch/mwe1/mwe1.gobra +++ b/verification/scratch/mwe1/mwe1.gobra @@ -3,7 +3,7 @@ // 1. We receive a message M over the network. // 2. We compute a "MAC tag" of M using K. // 3. We send the computed tag over the network. -package mwe1 +package mwe ////////////////////////////// I/O Actions ////////////////////////////// // cf. running-example/policy/actions.gobra @@ -96,27 +96,43 @@ pure func Low(v int) Observation { // this allows us to assume that the sensitivity postconditions are satisfied. ghost decreases -requires acc(History(), 1/2) && low(Pre(sig,*History())) -ensures acc(History(), 1/2) && low(Post(sig,*History())) +requires sig != nil && acc(History(), 1/2) && low(Pre(sig,*History())) +ensures acc(History(), 1/2) && low(Post(sig,*History())) func LowPost(sig ClassificationSpec) -type Collect domain { - func Post(ClassificationSpec, Trace) ObservationTrace - func Pre(ClassificationSpec, Trace) ObservationTrace - func pre_(Specification) Observation - func post_(Specification) Observation +// NOTE: these are the low projections mentioned in the paper +ghost +decreases +pure func pre_(spec Specification) Observation { + return match spec { + case Spec{?p, _}: p + } +} +ghost +decreases +pure func post_(spec Specification) Observation { + return match spec { + case Spec{_, ?q}: q + } +} - // NOTE: these are the low projections mentioned in the paper - axiom { - (forall p,q Observation :: {pre_(Spec{p,q})} pre_(Spec{p,q}) == p) && - (forall p,q Observation :: {post_(Spec{p,q})} post_(Spec{p,q}) == q) +ghost +decreases len(trace) +requires sig != nil +pure func Pre(sig ClassificationSpec, trace Trace) ObservationTrace { + return match trace { + case Empty{}: EmptyObs{} + case Snoc{?t, ?e}: SnocObs{Pre(sig, t), pre_(sig.Classification(e))} } +} - axiom { - (forall sig ClassificationSpec :: {Post(sig,Empty{})} Post(sig,Empty{}) == EmptyObs{}) && - (forall sig ClassificationSpec :: {Pre(sig,Empty{})} Pre(sig,Empty{}) == EmptyObs{}) && - (forall sig ClassificationSpec, t Trace, e Action :: {Post(sig,Snoc{t,e})} Post(sig,Snoc{t,e}) == SnocObs{Post(sig,t), post_(sig.Classification(e))}) && - (forall sig ClassificationSpec, t Trace, e Action :: {Pre(sig,Snoc{t,e})} Pre(sig,Snoc{t,e}) == SnocObs{Pre(sig,t), pre_(sig.Classification(e))}) +ghost +decreases len(trace) +requires sig != nil +pure func Post(sig ClassificationSpec, trace Trace) ObservationTrace { + return match trace { + case Empty{}: EmptyObs{} + case Snoc{?t, ?e}: SnocObs{Post(sig, t), post_(sig.Classification(e))} } } @@ -147,17 +163,13 @@ type IODSpec interface { pure Update(state, Action) state } +// NOTE: We don't need IsTrace but it is not clear how to translate Reaches. type Restriction domain { - func IsTrace(IODSpec, Trace) bool func Reaches(IODSpec, Trace, state) bool axiom { forall r IODSpec, t Trace, s state, a Action :: { Snoc{t, a}, Reaches(r, t, s) } Reaches(r, t, s) && r.Guard(s, a) ==> Reaches(r, Snoc{t, a}, r.Update(s, a)) } - - axiom { - forall r IODSpec, t Trace, s state :: {Reaches(r, t, s)} Reaches(r, t, s) ==> IsTrace(r,t) - } } // Our I/O spec. The state is the private key and the most recently received message. @@ -175,7 +187,7 @@ ghost decreases pure func (MWE1) Guard(s state, a Action) bool { return match a { - // NOTE: This makess our IOD spec well-formed, as what is allowed to be + // NOTE: This makes our IOD spec well-formed, as what is allowed to be // declassified is now deterministic in the previous I/O actions. case DeclassifyIO{?t}: t == MAC(s.key, s.lastMsg) case _: true @@ -231,6 +243,12 @@ pure func MAC(int, int) int // In the original example, I think this is done via the shared invariant. preserves acc(History()) && acc(s) && low(Pre(DefaultClassification{}, *History())) && Reaches(MWE1{}, *History(), *s) func authenticate(ghost s *state) { + // NOTE: forgetting the calls to either of `Declassify` and `LowPost` + // would result in a verification error *in the postcondition* (and not in + // the call to send), as here, compliance to the classification spec is + // not checked on every I/O call, but instead on the trace at the end of + // the function call. + m := Recv() LowPost(DefaultClassification{}) ghost s.lastMsg = m @@ -240,5 +258,8 @@ func authenticate(ghost s *state) { /* ghost */ Declassify(t) /* ghost */ LowPost(DefaultClassification{}) Send(t) + // The following call to `LowPost` is the only one not needed (here), + // as we don't need the postcondition of the last I/O call to verify + // the remainder of the function. /* ghost */ LowPost(DefaultClassification{}) } \ No newline at end of file diff --git a/verification/scratch/mwe2/mwe2.gobra b/verification/scratch/mwe2/mwe2.gobra index 8f1a1297f..477bdf79f 100644 --- a/verification/scratch/mwe2/mwe2.gobra +++ b/verification/scratch/mwe2/mwe2.gobra @@ -1,5 +1,5 @@ // Minimal working example 2. -package mwe2 +package mwe // Used to track position in protocol. type Place int @@ -10,13 +10,11 @@ pred RecvPerm(ghost p Place) // Returns the next place after calling `Recv` from `p`. // Avoids using an existential quantifier. ghost -requires RecvPerm(p) decreases pure func Recv_T(ghost p Place) Place // Used to refer to the received message. ghost -requires RecvPerm(p) decreases pure func Recv_R(ghost p Place) int @@ -27,51 +25,47 @@ pure func Recv_S(s state, m int) state { } requires token(p) && RecvPerm(p) -ensures token(old(Recv_T(p))) -ensures m == old(Recv_R(p)) +ensures token(next_p) && next_p == Recv_T(p) +ensures m == Recv_R(p) ensures low(m) -func Recv(ghost p Place) (m int) +func Recv(ghost p Place) (ghost next_p Place, m int) pred SendPerm(ghost p Place, t int) ghost -requires SendPerm(p, t) decreases pure func Send_T(ghost p Place, t int) Place requires token(p) && SendPerm(p, t) requires low(t) -ensures token(old(Send_T(p, t))) -func Send(ghost p Place, t int) +ensures token(next_p) && next_p == Send_T(p, t) +func Send(ghost p Place, t int) (ghost next_p Place) pred DeclassifyPerm(ghost p Place, tag int, t int) ghost -requires DeclassifyPerm(p, tag, t) decreases pure func Declassify_T(ghost p Place, tag int, t int) Place -// In order to make permitted declassifications deterministic in low data, -// we add low parameter `p` to "tag" declassifications. -// (Necessary as otherwise IOD spec Protocol2 would not be well-formed.) ghost requires token(p) && DeclassifyPerm(p, tag, t) requires low(tag) -ensures token(old(Declassify_T(p, tag, t))) +ensures token(next_p) && next_p == Declassify_T(p, tag, t) ensures low(t) decreases -func Declassify(ghost p Place, tag int, t int) +func Declassify(ghost p Place, tag int, t int) (ghost next_p Place) // "Linear" protocol. -pred Protocol1(ghost p Place, key int) { +pred Protocol1(ghost p0 Place, key int) { // 1. Receive a message. - RecvPerm(p) && + RecvPerm(p0) && let p1, m := Recv_T(p0), Recv_R(p0) in // 2. Compute MAC tag and declassify it. - DeclassifyPerm(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))) && + let tag := MAC(key, m) in + DeclassifyPerm(p1, m, tag) && let p2 := Declassify_T(p1, m, tag) in // 3. Send MAC tag over network. - SendPerm(Declassify_T(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))), MAC(key, Recv_R(p))) && + SendPerm(p2, tag) && let p3 := Send_T(p2, tag) in // 4. Restart. - Protocol1(Send_T(Declassify_T(Recv_T(p), Recv_R(p), MAC(key, Recv_R(p))), MAC(key, Recv_R(p))), key) + Protocol1(p3, key) } type state struct { @@ -81,15 +75,26 @@ type state struct { lastMsg3 int // 3rd } -pred Protocol2(ghost p Place, s state) { +pred Protocol2(ghost p0 Place, s0 state) { // Receive a message at any time. - RecvPerm(p) && Protocol2(Recv_T(p), Recv_S(s, Recv_R(p))) && + RecvPerm(p0) && + let p1, s1 := Recv_T(p0), Recv_S(s0, Recv_R(p0)) in Protocol2(p1, s1) && // NOTE: at the moment we can declassify things before receiving anything - // Declassify and send either the most or the 2nd most recently received message. - DeclassifyPerm(p, s.lastMsg1, MAC(s.key, s.lastMsg1)) && Protocol2(Declassify_T(p, s.lastMsg1, MAC(s.key, s.lastMsg1)), s) && - DeclassifyPerm(p, s.lastMsg2, MAC(s.key, s.lastMsg2)) && Protocol2(Declassify_T(p, s.lastMsg2, MAC(s.key, s.lastMsg2)), s) && - SendPerm(p, MAC(s.key, s.lastMsg1)) && Protocol2(Send_T(p, MAC(s.key, s.lastMsg1)), s) && - SendPerm(p, MAC(s.key, s.lastMsg2)) && Protocol2(Send_T(p, MAC(s.key, s.lastMsg2)), s) + // Declassify and send either the MAC tag of the most or the 2nd most + // recently received message. + let tag1, tag2 := MAC(s0.key, s0.lastMsg1), MAC(s0.key, s0.lastMsg2) in + + DeclassifyPerm(p0, s0.lastMsg1, tag1) && + let p1 := Declassify_T(p0, s0.lastMsg1, tag1) in Protocol2(p1, s0) && + + DeclassifyPerm(p0, s0.lastMsg2, tag2) && + let p1 := Declassify_T(p0, s0.lastMsg2, tag2) in Protocol2(p1, s0) && + + SendPerm(p0, tag1) && + let p1 := Send_T(p0, tag1) in Protocol2(p1, s0) && + + SendPerm(p0, tag2) && + let p1 := Send_T(p0, tag2) in Protocol2(p1, s0) } // Abstract function representing the computation of a MAC. @@ -103,30 +108,30 @@ func authenticate(ghost p0 Place, s state) { invariant token(p0) && Protocol2(p0, s) for { unfold Protocol2(p0, s) - ghost p1 := Recv_T(p0) - m1 := Recv(p0) + // ghost p1 := Recv_T(p0) + p1, m1 := Recv(p0) s = Recv_S(s, m1) unfold Protocol2(p1, s) - ghost p2 := Recv_T(p1) - m2 := Recv(p1) + // ghost p2 := Recv_T(p1) + p2, m2 := Recv(p1) s = Recv_S(s, m2) unfold Protocol2(p2, s) - ghost p3 := Recv_T(p2) - m3 := Recv(p2) + // ghost p3 := Recv_T(p2) + p3, m3 := Recv(p2) s = Recv_S(s, m3) // We can use m2, m3 here. m1 won't work. t := MAC(s.key, m3) unfold Protocol2(p3, s) - ghost p4 := Declassify_T(p3, m3, t) - Declassify(p3, m3, t) + // ghost p4 := Declassify_T(p3, m3, t) + ghost p4 := Declassify(p3, m3, t) unfold Protocol2(p4, s) - ghost p0 = Send_T(p4, t) - Send(p4, t) + // ghost p0 = Send_T(p4, t) + p0 = Send(p4, t) } } \ No newline at end of file From df0fed8f54f5204c0c2a29362356f5a40ccd8984 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 17 Dec 2024 15:37:59 +0100 Subject: [PATCH 004/104] Fix package names and remove state.lastMsg3 --- verification/scratch/mwe1/mwe1.gobra | 2 +- verification/scratch/mwe2/mwe2.gobra | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/verification/scratch/mwe1/mwe1.gobra b/verification/scratch/mwe1/mwe1.gobra index 97d747383..7d94d32d7 100644 --- a/verification/scratch/mwe1/mwe1.gobra +++ b/verification/scratch/mwe1/mwe1.gobra @@ -3,7 +3,7 @@ // 1. We receive a message M over the network. // 2. We compute a "MAC tag" of M using K. // 3. We send the computed tag over the network. -package mwe +package mwe1 ////////////////////////////// I/O Actions ////////////////////////////// // cf. running-example/policy/actions.gobra diff --git a/verification/scratch/mwe2/mwe2.gobra b/verification/scratch/mwe2/mwe2.gobra index 477bdf79f..b2844c7f0 100644 --- a/verification/scratch/mwe2/mwe2.gobra +++ b/verification/scratch/mwe2/mwe2.gobra @@ -1,5 +1,5 @@ // Minimal working example 2. -package mwe +package mwe2 // Used to track position in protocol. type Place int @@ -21,7 +21,7 @@ pure func Recv_R(ghost p Place) int // This is how the state is/should be updated after receiving message `m`. decreases pure func Recv_S(s state, m int) state { - return state { key: s.key, lastMsg1: m, lastMsg2: s.lastMsg1, lastMsg3: s.lastMsg2 } + return state { key: s.key, lastMsg1: m, lastMsg2: s.lastMsg1 } } requires token(p) && RecvPerm(p) @@ -72,7 +72,6 @@ type state struct { key int // the private key lastMsg1 int // 1st most recently received message lastMsg2 int // 2nd - lastMsg3 int // 3rd } pred Protocol2(ghost p0 Place, s0 state) { From 05df2069311b978620aceececac1662866aeed00 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 20 Jan 2025 20:36:46 +0100 Subject: [PATCH 005/104] Extend io-spec with declassification spec --- verification/io/io-spec.gobra | 44 ++++++++++++++++++++++++++++++++++- verification/io/values.gobra | 13 +++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index c7e98c92d..1ea1ac04c 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -33,7 +33,9 @@ pred (dp DataPlaneSpec) dp3s_iospec_ordered(s IO_dp3s_state_local, t Place) { dp.dp3s_iospec_bio3s_send(s, t) && dp.dp3s_iospec_bio3s_recv(s, t) && dp.dp3s_iospec_skip(s, t) && - dp.dp3s_iospec_stop(s, t) + dp.dp3s_iospec_stop(s, t) && + // SIF + dp.dp4s_iospec_bio4s_decl(s, t) } type Place int @@ -299,6 +301,46 @@ pred (dp DataPlaneSpec) dp3s_iospec_stop(s IO_dp3s_state_local, t Place) { true } +/* SIF: Declassification action. +I use "4s" to distinguish additions of the refined event system. */ +pred CBio_IN_bio4s(t Place, v IO_val) + +// SIF: Return next place +ghost +// SIF: I left out `requires CBio_IN_bio4s_decl(t, v)` as we deem it not necessary. +decreases +pure func CBio_IN_bio4s_decl_T(t Place, v IO_val) + +ghost +requires v.isIO_decl_val +requires dp.Valid() +decreases +pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, t Place, v IO_val) bool { + // SIF: cf. `hf_valid` in `router.gobra` + return v.IO_decl_val_sigma == nextMsgtermSpec( + dp.Asid(), + v.IO_decl_val_inif, + v.IO_decl_val_egif, + v.IO_decl_val_ts, + v.IO_decl_val_beta // uinfo + ) +} + +pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { + // SIF: just copying with `TriggerBodyIoEnter` for now. + forall v IO_val :: { TriggerBodyIoEnter(v) } ( + match v { + case IO_decl_val{?beta, ?ts, ?inif, ?egif, ?sigma}: + let _ignored := TriggerBodyIoEnter(v) in + (dp.Valid() && dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> + (CBio_IN_bio4s_decl(t, v) && + dp.dp3s_iospec_ordered(s, CBio_IN_bio4s_decl_T(t, v)))) + default: + true + } + ) +} + /** BIO operations **/ ghost decreases diff --git a/verification/io/values.gobra b/verification/io/values.gobra index 443c7a483..052105954 100644 --- a/verification/io/values.gobra +++ b/verification/io/values.gobra @@ -52,4 +52,17 @@ type IO_val adt { IO_Internal_val2_2 IO_pkt3 IO_Internal_val2_3 IO_ifs } + + /* SIF: Output (to env.) for declassification action. + This consists of the segment identifier `beta`, the timestamp `ts, the + ingress and egress interfaces `inif` and `egif` (resp.), and a hop + authenticator `sigma`. + Also see `router.gobra` for types. */ + IO_decl_val { + IO_decl_val_beta set[IO_msgterm] // uinfo + IO_decl_val_ts uint + IO_decl_val_inif option[IO_ifs] + IO_decl_val_egif option[IO_ifs] + IO_decl_val_sigma IO_msgterm + } } \ No newline at end of file From 389235fb071a59ac55272f67c25d5e73fa00f2fa Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 20 Jan 2025 21:17:55 +0100 Subject: [PATCH 006/104] Fix declassification spec --- verification/io/io-spec.gobra | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index 1ea1ac04c..3f80214f5 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -33,9 +33,9 @@ pred (dp DataPlaneSpec) dp3s_iospec_ordered(s IO_dp3s_state_local, t Place) { dp.dp3s_iospec_bio3s_send(s, t) && dp.dp3s_iospec_bio3s_recv(s, t) && dp.dp3s_iospec_skip(s, t) && - dp.dp3s_iospec_stop(s, t) && + dp.dp3s_iospec_stop(s, t) // && // SIF - dp.dp4s_iospec_bio4s_decl(s, t) + // dp.dp4s_iospec_bio4s_decl(s, t) } type Place int @@ -303,13 +303,13 @@ pred (dp DataPlaneSpec) dp3s_iospec_stop(s IO_dp3s_state_local, t Place) { /* SIF: Declassification action. I use "4s" to distinguish additions of the refined event system. */ -pred CBio_IN_bio4s(t Place, v IO_val) +pred CBio_IN_bio4s_decl(t Place, v IO_val) // SIF: Return next place ghost // SIF: I left out `requires CBio_IN_bio4s_decl(t, v)` as we deem it not necessary. decreases -pure func CBio_IN_bio4s_decl_T(t Place, v IO_val) +pure func CBio_IN_bio4s_decl_T(t Place, v IO_val) Place ghost requires v.isIO_decl_val @@ -317,28 +317,30 @@ requires dp.Valid() decreases pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, t Place, v IO_val) bool { // SIF: cf. `hf_valid` in `router.gobra` - return v.IO_decl_val_sigma == nextMsgtermSpec( + // NOTE: For some reason, formatting this differently leads to a parsing error. + return v.IO_decl_val_sigma == (nextMsgtermSpec( dp.Asid(), v.IO_decl_val_inif, v.IO_decl_val_egif, v.IO_decl_val_ts, - v.IO_decl_val_beta // uinfo - ) + v.IO_decl_val_beta)) } pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { // SIF: just copying with `TriggerBodyIoEnter` for now. + // NOTE: here as well we need some specific formatting forall v IO_val :: { TriggerBodyIoEnter(v) } ( match v { case IO_decl_val{?beta, ?ts, ?inif, ?egif, ?sigma}: let _ignored := TriggerBodyIoEnter(v) in (dp.Valid() && dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> (CBio_IN_bio4s_decl(t, v) && - dp.dp3s_iospec_ordered(s, CBio_IN_bio4s_decl_T(t, v)))) + dp.dp3s_iospec_ordered( + s, + CBio_IN_bio4s_decl_T(t, v)))) default: true - } - ) + }) } /** BIO operations **/ From 1831b532ee6272a039e1e880a3b6ed257d20db8e Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 20 Jan 2025 21:22:22 +0100 Subject: [PATCH 007/104] Fix adding decl to dp3s_iospec_ordered --- verification/io/io-spec.gobra | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index 3f80214f5..db476ac19 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -33,9 +33,9 @@ pred (dp DataPlaneSpec) dp3s_iospec_ordered(s IO_dp3s_state_local, t Place) { dp.dp3s_iospec_bio3s_send(s, t) && dp.dp3s_iospec_bio3s_recv(s, t) && dp.dp3s_iospec_skip(s, t) && - dp.dp3s_iospec_stop(s, t) // && - // SIF - // dp.dp4s_iospec_bio4s_decl(s, t) + dp.dp3s_iospec_stop(s, t) && + // SIF: + dp.dp4s_iospec_bio4s_decl(s, t) } type Place int From 094d917a31775abfe3550903e6c29df5a7296479 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 22 Jan 2025 16:19:57 +0100 Subject: [PATCH 008/104] Start implementing IOD spec What is missing is the `AtomicDecl` and `InternalDecl` event in `io-spec-atomic-events.gobra` and `io-spec-abstract-transitions.gobra`. --- verification/io/io-spec.gobra | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index db476ac19..ead2787cb 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -301,7 +301,7 @@ pred (dp DataPlaneSpec) dp3s_iospec_stop(s IO_dp3s_state_local, t Place) { true } -/* SIF: Declassification action. +/* SIF: Declassification specification. I use "4s" to distinguish additions of the refined event system. */ pred CBio_IN_bio4s_decl(t Place, v IO_val) @@ -309,7 +309,7 @@ pred CBio_IN_bio4s_decl(t Place, v IO_val) ghost // SIF: I left out `requires CBio_IN_bio4s_decl(t, v)` as we deem it not necessary. decreases -pure func CBio_IN_bio4s_decl_T(t Place, v IO_val) Place +pure func dp4s_iospec_bio4s_decl_T(t Place, v IO_val) Place ghost requires v.isIO_decl_val @@ -337,7 +337,7 @@ pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { (CBio_IN_bio4s_decl(t, v) && dp.dp3s_iospec_ordered( s, - CBio_IN_bio4s_decl_T(t, v)))) + dp4s_iospec_bio4s_decl_T(t, v)))) default: true }) @@ -362,4 +362,11 @@ requires token(t) && CBio_IN_bio3s_exit(t, v) ensures token(old(dp3s_iospec_bio3s_exit_T(t, v))) func Exit(ghost t Place, ghost v IO_val) +// SIF: declassification action +ghost +decreases +requires token(t) && CBio_IN_bio4s_decl(t, v) +ensures token(dp4s_iospec_bio4s_decl_T(t, v)) +func Decl(ghost t Place, ghost v IO_val) + /** End of helper functions to perfrom BIO operations **/ From 0e6eff1e4af9836af3f35eb4d7862894651c9d06 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 22 Jan 2025 17:07:51 +0100 Subject: [PATCH 009/104] Start implementing classification spec Pre- and postconditions still need to be "lifted" to the atomic and internal events. `WriteTo` may also need a spec --- router/dataplane.go | 12 ++++++++++++ verification/io/io-spec.gobra | 3 +++ 2 files changed, 15 insertions(+) diff --git a/router/dataplane.go b/router/dataplane.go index 1ade5d5d7..6bfd8a2cc 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -145,6 +145,14 @@ type BatchConn interface { // @ ensures err == nil ==> // @ forall i int :: { &msgs[i] } 0 <= i && i < n ==> // @ MsgToAbsVal(&msgs[i], ingressID) == old(MultiReadBioIO_val(place, n)[i]) + // SIF: classification spec + // NOTE: The postcondition for recv specifies that received messages are low. + // TODO: I have decided to mark the abstraction as low for now. + // And also used MultiReadBioIOI_val instead of MsgToAbsVal to match what is + // used in permissions. + // @ ensures err == nil ==> + // @ forall i int :: { MutliReadBioIO_val(place, n)[i] } 0 <= i && i < n ==> + // @ low(old(MultiReadBioIO_val(place, n)[i])) ReadBatch(msgs underlayconn.Messages /*@, ghost ingressID uint16, ghost prophecyM int, ghost place io.Place @*/) (n int, err error) // @ requires acc(addr.Mem(), _) // @ requires acc(Mem(), _) @@ -161,6 +169,10 @@ type BatchConn interface { // preconditions for IO-spec: // @ requires MsgToAbsVal(&msgs[0], egressID) == ioAbsPkts // @ requires io.token(place) && io.CBioIO_bio3s_send(place, ioAbsPkts) + // SIF: classification spec + // NOTE: I mark `ioAbsPkts` instead of `MsgToAbsVal(...)` as low here to + // match variable used in send permission. + // @ requires low(ioAbsPkts) // @ ensures acc(msgs[0].Mem(), R50) && msgs[0].HasActiveAddr() // @ ensures acc(sl.Bytes(msgs[0].GetFstBuffer(), 0, len(msgs[0].GetFstBuffer())), R50) // @ ensures err == nil ==> 0 <= n && n <= len(msgs) diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index ead2787cb..c11260589 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -363,10 +363,13 @@ ensures token(old(dp3s_iospec_bio3s_exit_T(t, v))) func Exit(ghost t Place, ghost v IO_val) // SIF: declassification action +// TODO: maybe it's a good idea to split up sigma and p in IO_val ghost decreases requires token(t) && CBio_IN_bio4s_decl(t, v) +requires v.isIO_decl_val && low(v.IO_decl_val_inif) && low(v.IO_decl_val_egif) && low(v.IO_decl_val_ts) && low(v.IO_decl_val_beta) ensures token(dp4s_iospec_bio4s_decl_T(t, v)) +ensures low(v.IO_decl_val_sigma) func Decl(ghost t Place, ghost v IO_val) /** End of helper functions to perfrom BIO operations **/ From b0795a97a62257788e924c311bd741989406bb9c Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 29 Jan 2025 18:51:31 +0100 Subject: [PATCH 010/104] Mostly finished implementing security policy More data needs to be asserted low, of course. `DeclEvent` needs to be verified once slayers problem is worked out --- pkg/slayers/scion_spec.gobra | 22 +++++++++-------- router/dataplane.go | 1 + router/io-spec-abstract-transitions.gobra | 14 +++++++++++ router/io-spec-atomic-events.gobra | 30 +++++++++++++++++++++++ verification/io/io-spec.gobra | 15 +++++++++--- 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/pkg/slayers/scion_spec.gobra b/pkg/slayers/scion_spec.gobra index a66bce011..1dea296ca 100644 --- a/pkg/slayers/scion_spec.gobra +++ b/pkg/slayers/scion_spec.gobra @@ -412,20 +412,21 @@ requires acc(sl.Bytes(ub, 0, len(ub)), _) decreases func (s *SCION) EqAbsHeader(ub []byte) bool { return unfolding acc(s.Mem(ub), _) in - let low := CmnHdrLen+s.AddrHdrLenSpecInternal() in + // SIF: rename due to conflict with low assertion + let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := s.HdrLen*LineLen in - GetAddressOffset(ub) == low && + GetAddressOffset(ub) == low_ && GetLength(ub) == int(high) && // Might be worth introducing EqAbsHeader as an interface method on Path // to avoid doing these casts, especially when we add support for EPIC. typeOf(s.Path) == (*scion.Raw) && - unfolding acc(s.Path.Mem(ub[low:high]), _) in + unfolding acc(s.Path.Mem(ub[low_:high]), _) in unfolding acc(sl.Bytes(ub, 0, len(ub)), _) in - let _ := Asserting(forall k int :: {&ub[low:high][k]} 0 <= k && k < high ==> - &ub[low:high][k] == &ub[low + k]) in - let _ := Asserting(forall k int :: {&ub[low:high][:scion.MetaLen][k]} 0 <= k && k < scion.MetaLen ==> - &ub[low:high][:scion.MetaLen][k] == &ub[low:high][k]) in - let metaHdr := scion.DecodedFrom(binary.BigEndian.Uint32(ub[low:high][:scion.MetaLen])) in + let _ := Asserting(forall k int :: {&ub[low_:high][k]} 0 <= k && k < high ==> + &ub[low_:high][k] == &ub[low_ + k]) in + let _ := Asserting(forall k int :: {&ub[low_:high][:scion.MetaLen][k]} 0 <= k && k < scion.MetaLen ==> + &ub[low_:high][:scion.MetaLen][k] == &ub[low_:high][k]) in + let metaHdr := scion.DecodedFrom(binary.BigEndian.Uint32(ub[low_:high][:scion.MetaLen])) in let seg1 := int(metaHdr.SegLen[0]) in let seg2 := int(metaHdr.SegLen[1]) in let seg3 := int(metaHdr.SegLen[2]) in @@ -442,10 +443,11 @@ requires acc(s.Mem(ub), _) decreases func (s *SCION) ValidScionInitSpec(ub []byte) bool { return unfolding acc(s.Mem(ub), _) in - let low := CmnHdrLen+s.AddrHdrLenSpecInternal() in + // SIF: rename due to conflict with low assertion + let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := s.HdrLen*LineLen in typeOf(s.Path) == (*scion.Raw) && - s.Path.(*scion.Raw).GetBase(ub[low:high]).WeaklyValid() + s.Path.(*scion.Raw).GetBase(ub[low_:high]).WeaklyValid() } // Checks if the common path header is valid in the serialized scion packet. diff --git a/router/dataplane.go b/router/dataplane.go index 6bfd8a2cc..a778731c2 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -159,6 +159,7 @@ type BatchConn interface { // @ preserves acc(sl.Bytes(b, 0, len(b)), R10) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() + // SIF: add to classification spec later as well (bfdSend) WriteTo(b []byte, addr *net.UDPAddr) (n int, err error) // @ requires acc(Mem(), _) // (VerifiedSCION) opted for less reusable spec for WriteBatch for diff --git a/router/io-spec-abstract-transitions.gobra b/router/io-spec-abstract-transitions.gobra index 8aa346ff3..47ce50408 100644 --- a/router/io-spec-abstract-transitions.gobra +++ b/router/io-spec-abstract-transitions.gobra @@ -251,3 +251,17 @@ func XoverEvent(oldPkt io.IO_pkt2, ingressID option[io.IO_ifs], newPkt io.IO_pkt } AtomicXover(oldPkt, ingressID, newPkt, egressID, ioLock, ioSharedArg, dp) } + +ghost +// NOTE: Not sure if I need this +// requires dp.Valid() +// TODO: Not sure if we need this next to `AtomicDecl` +requires low(beta) && low(ts) && low(inif) && low(egif) +requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) +preserves acc(ioLock.LockP(), _) +preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> +ensures low(sigma) +decreases +func DeclEvent(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { + AtomicDecl(beta, ts, inif, egif, sigma, ioLock, ioSharedArg, dp) +} \ No newline at end of file diff --git a/router/io-spec-atomic-events.gobra b/router/io-spec-atomic-events.gobra index fca20b964..ee6d03477 100644 --- a/router/io-spec-atomic-events.gobra +++ b/router/io-spec-atomic-events.gobra @@ -155,6 +155,36 @@ func AtomicXover(oldPkt io.IO_pkt2, ingressID option[io.IO_ifs], newPkt io.IO_pk UpdateElemWitness(s.obuf, ioSharedArg.OBufY, egressID, newPkt) ghost *ioSharedArg.State = io.dp3s_add_obuf(s, egressID, newPkt) ghost *ioSharedArg.Place = tN + fold SharedInv!< dp, ioSharedArg !>() + ghost ioLock.Unlock() +} + +ghost +// NOTE: I don't think I need this here +// requires dp.Valid() +requires low(beta) && low(ts) && low(inif) && low(egif) +requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) +preserves acc(ioLock.LockP(), _) +preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> +ensures low(sigma) +// NOTE: Not sure if I need this +decreases _ +func AtomicDecl(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { + ghost ioLock.Lock() + unfold SharedInv!< dp, ioSharedArg !>() + + t, s := *ioSharedArg.Place, *ioSharedArg.State + ghost tag := io.IO_val(io.IO_decl_val{beta, ts, inif, egif, sigma}) + + assert dp.dp4s_iospec_bio4s_decl_guard(s, t, tag) + unfold dp.dp3s_iospec_ordered(s, t) + unfold dp.dp4s_iospec_bio4s_decl(s, t) + + io.TriggerBodyIoDecl(tag) + tN := io.dp4s_iospec_bio4s_decl_T(t, tag) + io.Decl(t, tag) + ghost *ioSharedArg.Place = tN + fold SharedInv!< dp, ioSharedArg !>() ghost ioLock.Unlock() } \ No newline at end of file diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index c11260589..f973804d8 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -313,7 +313,8 @@ pure func dp4s_iospec_bio4s_decl_T(t Place, v IO_val) Place ghost requires v.isIO_decl_val -requires dp.Valid() +// NOTE: Not sure I need this here +// requires dp.Valid() decreases pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, t Place, v IO_val) bool { // SIF: cf. `hf_valid` in `router.gobra` @@ -329,11 +330,13 @@ pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { // SIF: just copying with `TriggerBodyIoEnter` for now. // NOTE: here as well we need some specific formatting - forall v IO_val :: { TriggerBodyIoEnter(v) } ( + forall v IO_val :: { TriggerBodyIoDecl(v) } ( match v { case IO_decl_val{?beta, ?ts, ?inif, ?egif, ?sigma}: - let _ignored := TriggerBodyIoEnter(v) in - (dp.Valid() && dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> + let _ignored := TriggerBodyIoDecl(v) in + // NOTE: Not sure I need this here + // (dp.Valid() && dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> + (dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> (CBio_IN_bio4s_decl(t, v) && dp.dp3s_iospec_ordered( s, @@ -343,6 +346,10 @@ pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { }) } +ghost +decreases +pure func TriggerBodyIoDecl(v IO_val) BogusTrigger { return BogusTrigger{} } + /** BIO operations **/ ghost decreases From 2e41d1224f749ec7a516f80fc6eb780650738d06 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 29 Jan 2025 22:08:10 +0100 Subject: [PATCH 011/104] Verify private/topology/underlay --- private/topology/underlay/defs.go | 3 +++ verification/dependencies/strings/strings.gobra | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index ad6ffdf18..8dae1043d 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -47,6 +47,7 @@ const ( EndhostPort = 30041 ) +// @ requires low(o) func (o Type) String() string { switch o { case UDPIPv4: @@ -60,6 +61,7 @@ func (o Type) String() string { } } +// @ requires low(s) func TypeFromString(s string) (Type, error) { switch strings.ToLower(s) { case strings.ToLower(UDPIPv4Name): @@ -94,6 +96,7 @@ func (ot Type) MarshalJSON() ([]byte, error) { return json.Marshal(ot.String()) } +// @ requires low(ot) func (ot Type) IsUDP() bool { switch ot { case UDPIPv4, UDPIPv6, UDPIPv46: diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index 13fb98747..0b62814c6 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -115,8 +115,9 @@ decreases _ func ToUpper(s string) string // ToLower returns s with all Unicode letters mapped to their lower case. +ensures low(s) ==> low(res) decreases _ -func ToLower(s string) string +func ToLower(s string) (res string) // ToTitle returns a copy of the string s with all Unicode letters mapped to // their Unicode title case. From 82a323901c93482f4b1b058e14abdbb8cca8d8ed Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 30 Jan 2025 20:39:52 +0100 Subject: [PATCH 012/104] Finish implementing security policy `DeclEvent` was removed as it just copied `AtomicDecl`. It seems that we don't need to perform that much translation of the pre- and postconditions as the other events. --- router/io-spec-abstract-transitions.gobra | 14 -------------- router/io-spec-atomic-events.gobra | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/router/io-spec-abstract-transitions.gobra b/router/io-spec-abstract-transitions.gobra index 47ce50408..8aa346ff3 100644 --- a/router/io-spec-abstract-transitions.gobra +++ b/router/io-spec-abstract-transitions.gobra @@ -251,17 +251,3 @@ func XoverEvent(oldPkt io.IO_pkt2, ingressID option[io.IO_ifs], newPkt io.IO_pkt } AtomicXover(oldPkt, ingressID, newPkt, egressID, ioLock, ioSharedArg, dp) } - -ghost -// NOTE: Not sure if I need this -// requires dp.Valid() -// TODO: Not sure if we need this next to `AtomicDecl` -requires low(beta) && low(ts) && low(inif) && low(egif) -requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) -preserves acc(ioLock.LockP(), _) -preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> -ensures low(sigma) -decreases -func DeclEvent(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { - AtomicDecl(beta, ts, inif, egif, sigma, ioLock, ioSharedArg, dp) -} \ No newline at end of file diff --git a/router/io-spec-atomic-events.gobra b/router/io-spec-atomic-events.gobra index ee6d03477..40f7db218 100644 --- a/router/io-spec-atomic-events.gobra +++ b/router/io-spec-atomic-events.gobra @@ -167,7 +167,7 @@ requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) preserves acc(ioLock.LockP(), _) preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> ensures low(sigma) -// NOTE: Not sure if I need this +// NOTE: Seems to be needed bc. lock might not terminate decreases _ func AtomicDecl(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { ghost ioLock.Lock() From 96202624ad1c5353082f30cad790fae71f524016 Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 1 Feb 2025 14:22:31 +0100 Subject: [PATCH 013/104] Add additional annotations to private/topology/underlay In particular, add postconditions asserting that results are low wherever it is applicable, in order to make the contracts more precise. This is in anticipation of needing this information for further verification. Note that I still have some concerns regarding the pre- and postconditions of `fmt.Sprintf` and `serrors.New` which are elaborated in comments. --- pkg/private/serrors/serrors_spec.gobra | 4 ++++ private/topology/underlay/defs.go | 14 +++++++++++--- verification/dependencies/fmt/fmt.gobra | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 3bd25fe75..e727f8a41 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -90,8 +90,12 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. +// SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. +requires low(msg) && low(len(errCtx)) +requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() +ensures low(res) decreases func New(msg string, errCtx ...interface{}) (res error) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 8dae1043d..75a9d9a0a 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -47,8 +47,10 @@ const ( EndhostPort = 30041 ) +// SIF: Branch conditions (mostly) need to be `low` // @ requires low(o) -func (o Type) String() string { +// @ ensures low(res) +func (o Type) String() (res string) { switch o { case UDPIPv4: return UDPIPv4Name @@ -61,8 +63,10 @@ func (o Type) String() string { } } +// SIF: Branch conditions (mostly) need to be `low` // @ requires low(s) -func TypeFromString(s string) (Type, error) { +// @ ensures low(t) && low(err) +func TypeFromString(s string) (t Type, err error) { switch strings.ToLower(s) { case strings.ToLower(UDPIPv4Name): return UDPIPv4, nil @@ -75,6 +79,8 @@ func TypeFromString(s string) (Type, error) { } } +// SIF: I am not annotating these methods (for now), as they can't be called +// from the router anyway (cf. verification/utils/definitions/definitions.gobra) // @ trusted // @ requires Uncallable() func (ot *Type) UnmarshalJSON(data []byte) error { @@ -96,8 +102,10 @@ func (ot Type) MarshalJSON() ([]byte, error) { return json.Marshal(ot.String()) } +// SIF: Branch conditions (mostly) need to be `low` // @ requires low(ot) -func (ot Type) IsUDP() bool { +// @ ensures low(res) +func (ot Type) IsUDP() (res bool) { switch ot { case UDPIPv4, UDPIPv6, UDPIPv46: return true diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index eefea525f..de13ff66b 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -18,12 +18,25 @@ package fmt import . "github.com/scionproto/scion/verification/utils/definitions" +// SIF: I couldn't encode low(format) && forall i :: low(v[i]) ==> low(res), +// so (at least for now) this uses pre- and postcondition. +// SIF: This precondition might not be complete. +// - We might want to require low(len(v)) or something similar, tho +// `format` should already prescribe how many values are used +// - We might also need the order of the elements to be low +requires low(format) +// TODO: Maybe split preserves as now we double precondition? +// SIF: I originally introduced `LowBytes`, a version of sl.Bytes that asserted +// the elements to be low as well. However, I was not able to fold the predicate, +// as the slice is implicitly created. +requires forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) && low(v[i]) preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) +ensures low(res) decreases _ -func Sprintf(format string, v ...interface{}) string +func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() From 4f88e97bf73023085f7829a75c962428d9e2c107 Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 1 Feb 2025 15:27:13 +0100 Subject: [PATCH 014/104] Add minimum annotations to verify private/topology --- private/topology/linktype.go | 20 +++++++++++++++++--- verification/utils/slices/slices.gobra | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index d8b47579f..30ac5ee32 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -44,6 +44,7 @@ const ( Peer LinkType = 4 ) +// @ requires low(l) // @ decreases func (l LinkType) String() string { if l == Unset { @@ -59,17 +60,23 @@ func (l LinkType) String() string { // LinkTypeFromString returns the numerical link type associated with a string description. If the // string is not recognized, an Unset link type is returned. The matching is case-insensitive. +// @ requires low(s) // @ decreases func LinkTypeFromString(s string) (res LinkType) { var l /*@@@*/ LinkType tmp := []byte(s) - //@ fold sl.Bytes(tmp, 0, len(tmp)) + // SIF: For now I have to make this assumption, see Gobra issue #831 + // Note that we can already infer low(len(tmp)), as the lengths are related + // in the Viper encoding, but not the contents. + //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sl.LowBytes(tmp, 0, len(tmp)) if err := l.UnmarshalText(tmp); err != nil { return Unset } return l } +// @ requires low(l) // @ ensures (l == Core || l == Parent || l == Child || l == Peer) == (err == nil) // @ ensures err == nil ==> sl.Bytes(res, 0, len(res)) // @ ensures err != nil ==> err.ErrorMem() @@ -97,13 +104,20 @@ func (l LinkType) MarshalText() (res []byte, err error) { } } +// SIF: For now I assume that low(len(data)) and sl.LowBytes... suffice to infer +// that string(data) is low. See Gobra issue #832 +// @ requires low(len(data)) +// @ requires acc(sl.LowBytes(data, 0, len(data)), R15) // @ preserves acc(l) -// @ preserves acc(sl.Bytes(data, 0, len(data)), R15) +// @ ensures acc(sl.Bytes(data, 0, len(data)), R15) // @ ensures err != nil ==> err.ErrorMem() +// @ ensures low(err) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { - //@ unfold acc(sl.Bytes(data, 0, len(data)), R15) + //@ unfold acc(sl.LowBytes(data, 0, len(data)), R15) //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R15) + // SIF: For now I have to make this assumption, see Gobra issue #832 + //@ assume low(string(data)) switch strings.ToLower(string(data)) { case "core": *l = Core diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 8c7c4ac15..f89de81f5 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -32,6 +32,16 @@ pred Bytes(s []byte, start int, end int) { forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) } +// SIF: `Bytes` with the addition of asserting the elements to be low. +pred LowBytes(s []byte, start int, end int) { + // start inclusive + 0 <= start && + start <= end && + // end exclusive + end <= cap(s) && + forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) && low(s[i]) +} + pure requires acc(Bytes(s, start, end), _) requires start <= i && i < end From c8b7b5ccf2a390b0b822eeb6c667dbfa3aea9d30 Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 1 Feb 2025 16:20:08 +0100 Subject: [PATCH 015/104] Add appropriate postconditions to private/topology As for private/topology/underlay, I have added these to make the contracts more precise, as I anticipate I will need this for further verification Note that at the moment, we still need a lot of `assume` statements due to Gobra issues #831 and #832. Also, `LinkType.String` doesn't have an appropriate postcondition yet, as I couldn't assert (nor assume) low(err.Error()) for a low `err`. --- private/topology/linktype.go | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 30ac5ee32..0512408a4 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -45,8 +45,9 @@ const ( ) // @ requires low(l) +// SIF: I cannot assert low(res) yet bc. I can't assert (nor assume) low(err.Error()) in if-block // @ decreases -func (l LinkType) String() string { +func (l LinkType) String() (res string) { if l == Unset { return "unset" } @@ -54,13 +55,15 @@ func (l LinkType) String() string { if err != nil { return err.Error() } - //@ unfold sl.Bytes(s, 0, len(s)) + //@ unfold sl.LowBytes(s, 0, len(s)) + //@ assume low(string(s)) return string(s) } // LinkTypeFromString returns the numerical link type associated with a string description. If the // string is not recognized, an Unset link type is returned. The matching is case-insensitive. // @ requires low(s) +// @ ensures low(res) // @ decreases func LinkTypeFromString(s string) (res LinkType) { var l /*@@@*/ LinkType @@ -78,26 +81,37 @@ func LinkTypeFromString(s string) (res LinkType) { // @ requires low(l) // @ ensures (l == Core || l == Parent || l == Child || l == Peer) == (err == nil) -// @ ensures err == nil ==> sl.Bytes(res, 0, len(res)) +// @ ensures err == nil ==> sl.LowBytes(res, 0, len(res)) // @ ensures err != nil ==> err.ErrorMem() +// SIF: To make the postconditions as precise as possible, I have not put this +// assertion behind `err == nil` or `err != nil` (resp.) +// Only for LowBytes(...) I have, as that only makes sense when `res != nil`, +// and I have added `low(res)` for `err != nil` appropriately. +// @ ensures low(len(res)) && low(err) +// @ ensures err != nil ==> low(res) // @ decreases func (l LinkType) MarshalText() (res []byte, err error) { switch l { case Core: tmp := []byte("core") - //@ fold sl.Bytes(tmp, 0, len(tmp)) + // SIF: ATM we need this assumption, see Gobra issue #831 + //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sl.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Parent: tmp := []byte("parent") - //@ fold sl.Bytes(tmp, 0, len(tmp)) + //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sl.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Child: tmp := []byte("child") - //@ fold sl.Bytes(tmp, 0, len(tmp)) + //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sl.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Peer: tmp := []byte("peer") - //@ fold sl.Bytes(tmp, 0, len(tmp)) + //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sl.LowBytes(tmp, 0, len(tmp)) return tmp, nil default: return nil, serrors.New("invalid link type") @@ -110,7 +124,10 @@ func (l LinkType) MarshalText() (res []byte, err error) { // @ requires acc(sl.LowBytes(data, 0, len(data)), R15) // @ preserves acc(l) // @ ensures acc(sl.Bytes(data, 0, len(data)), R15) -// @ ensures err != nil ==> err.ErrorMem() +// @ ensures err != nil ==> err.ErrorMem() +// SIF: As *l remains unchanged if err != nil, and we don't know if low(*l) before +// @ ensures err == nil ==> low(*l) +// SIF: We need `low(err)` regardless of `err ?= nil` as we use it in branch conditions. // @ ensures low(err) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { From 799cd4d73866f0a9fa21839edae9cefa110fb73b Mon Sep 17 00:00:00 2001 From: henriman Date: Sun, 2 Feb 2025 00:28:43 +0100 Subject: [PATCH 016/104] Add `low` precondition to `fmt.Sprintf` I forgot to require all elements of `v` to be low. This also required the addition of another assumption in `private/topology/underlay`, as Gobra can't infer that all elements of a slice created from low values must be low (cf. Gobra issue #835) --- pkg/private/serrors/serrors_spec.gobra | 4 ++-- private/topology/underlay/defs.go | 3 +++ verification/dependencies/fmt/fmt.gobra | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index e727f8a41..86f835cb4 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -92,8 +92,8 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // with this method. // SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. requires low(msg) && low(len(errCtx)) -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) -preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) && low(errCtx[i]) +ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() ensures low(res) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 75a9d9a0a..98f7e7241 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -75,6 +75,9 @@ func TypeFromString(s string) (t Type, err error) { case strings.ToLower(UDPIPv46Name): return UDPIPv46, nil default: + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"type", s} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return Invalid, serrors.New("Unknown underlay type", "type", s) } } diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index de13ff66b..6815d9ada 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -30,7 +30,7 @@ requires low(format) // the elements to be low as well. However, I was not able to fold the predicate, // as the slice is implicitly created. requires forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) && low(v[i]) -preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) +ensures forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) From 9863f30e556cbd77e6287acf22a66a6b7e98c475 Mon Sep 17 00:00:00 2001 From: henriman Date: Sun, 2 Feb 2025 00:40:08 +0100 Subject: [PATCH 017/104] Add assumption due to Gobra issue #835 --- private/topology/linktype.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 0512408a4..82b777e8c 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -145,6 +145,9 @@ func (l *LinkType) UnmarshalText(data []byte) (err error) { case "peer": *l = Peer default: + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"linkType", string(data)} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return serrors.New("invalid link type", "linkType", string(data)) } return nil From ccd088c156e6c10fdd6f435ab7fb6ecf9b935390 Mon Sep 17 00:00:00 2001 From: henriman Date: Sun, 2 Feb 2025 01:03:23 +0100 Subject: [PATCH 018/104] Add minimum annotations to pkg/addr There still remain a lot of assumptions due to Gobra issues --- pkg/addr/fmt.go | 2 + pkg/addr/host.go | 37 +++++++- pkg/addr/host_spec.gobra | 5 ++ pkg/addr/isdas.go | 87 ++++++++++++++++--- pkg/addr/isdas_spec.gobra | 8 ++ pkg/private/serrors/serrors_spec.gobra | 6 +- .../dependencies/encoding/encoding.gobra | 7 +- verification/dependencies/flag/flag.gobra | 9 +- verification/dependencies/fmt/fmt.gobra | 2 + verification/dependencies/net/ip.gobra | 3 + verification/dependencies/strconv/atoi.gobra | 1 + .../dependencies/strings/strings.gobra | 6 +- 12 files changed, 156 insertions(+), 17 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index f534b82ea..a86f617fd 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -112,6 +112,7 @@ func FormatAS(as_ AS, opts ...FormatOption) string { } // @ requires as_.inRange() +// @ requires low(as_) // @ decreases func fmtAS(as_ AS, sep string) string { if !as_.inRange() { @@ -132,6 +133,7 @@ func fmtAS(as_ AS, sep string) string { // @ b.ZeroBuilderIsReadyToUse() b.Grow(maxLen) // @ invariant b.Mem() + // @ invariant low(i) // @ decreases asParts - i for i := 0; i < asParts; i++ { if i > 0 { diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 8efeb76aa..b42e16c62 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -40,6 +40,7 @@ const ( HostTypeSVC ) +// @ requires low(t) // @ requires isValidHostAddrType(t) // @ decreases func (t HostAddrType) String() string { @@ -107,6 +108,10 @@ type HostAddr interface { //@ decreases Copy() (res HostAddr) + // SIF: As every implementation casts `o` to its type, and casts require the + // argument to be low (at the moment at least), I think it's OK to require + // this for every implementing type. + //@ requires low(o) //@ preserves acc(Mem(), R13) && acc(o.Mem(), R13) //@ decreases Equal(o HostAddr) bool @@ -116,6 +121,9 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer + //@ pred StringLow() + + //@ requires StringLow() //@ preserves acc(Mem(), R13) //@ decreases String() string @@ -156,6 +164,8 @@ func (h HostNone) Copy() (res HostAddr) { return tmp } +// SIF: The Viper encoding contains a non-low branch condition if not `low(o)` +// @ requires low(o) // @ ensures res == (typeOf(o) == type[HostNone]) // @ decreases func (h HostNone) Equal(o HostAddr) (res bool) { @@ -214,6 +224,8 @@ func (h HostIPv4) Copy() (res HostAddr) { return tmp } +// SIF: Does this make sense considering HostIPv4 is byte slice underneath? +// @ requires low(o) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -282,6 +294,7 @@ func (h HostIPv6) Copy() (res HostAddr) { return tmp } +// @ requires low(o) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -311,6 +324,7 @@ type HostSVC uint16 // SVC addresses, use BS_A, PS_A, CS_A, and SB_A; shorthand versions without // the _A suffix (e.g., PS) also return anycast SVC addresses. For multicast, // use BS_M, PS_M, CS_M, and SB_M. +// @ requires low(str) // @ decreases func HostSVCFromString(str string) HostSVC { var m HostSVC @@ -366,13 +380,15 @@ func (h HostSVC) IP() (res net.IP) { return nil } +// @ ensures low(h) ==> low(res) // @ decreases -func (h HostSVC) IsMulticast() bool { +func (h HostSVC) IsMulticast() (res bool) { return (h & SVCMcast) != 0 } +// @ ensures low(h) ==> low(res) // @ decreases -func (h HostSVC) Base() HostSVC { +func (h HostSVC) Base() (res HostSVC) { return h & ^SVCMcast } @@ -388,12 +404,14 @@ func (h HostSVC) Copy() (res HostAddr) { return h } +// @ requires low(o) // @ decreases func (h HostSVC) Equal(o HostAddr) bool { ha, ok := o.(HostSVC) return ok && h == ha } +// @ requires low(h) // @ decreases func (h HostSVC) String() string { name := h.BaseString() @@ -401,13 +419,21 @@ func (h HostSVC) String() string { if h.IsMulticast() { cast = 'M' } + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(name) + //@ assert low(cast) + //@ assert low(uint16(h)) + //@ ghost errCtx := []interface{}{name, cast, uint16(h)} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return fmt.Sprintf("%v %c (0x%04x)", name, cast, uint16(h)) } // BaseString returns the upper case name of the service. For hosts or unrecognized services, it // returns UNKNOWN. +// @ requires low(h) +// @ ensures low(res) // @ decreases -func (h HostSVC) BaseString() string { +func (h HostSVC) BaseString() (res string) { switch h.Base() { case SvcDS: return "DS" @@ -425,6 +451,7 @@ func (h HostSVC) Network() string { return "" } +// @ requires low(htype) // @ requires acc(b) // @ requires isValidHostAddrType(htype) // @ requires len(b) == sizeOfHostAddrType(htype) @@ -466,6 +493,7 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { } } +// @ requires low(len(ip)) // @ requires acc(ip) // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 // @ ensures res.Mem() @@ -483,6 +511,7 @@ func HostFromIP(ip net.IP) (res HostAddr) { return tmp } +// @ requires low(s) // @ ensures res.Mem() // @ decreases func HostFromIPStr(s string) (res HostAddr) { @@ -495,6 +524,7 @@ func HostFromIPStr(s string) (res HostAddr) { return HostFromIP(ip) } +// @ requires low(htype) // @ requires isValidHostAddrType(htype) // @ decreases func HostLen(htype HostAddrType) (uint8, error) { @@ -514,6 +544,7 @@ func HostLen(htype HostAddrType) (uint8, error) { return length, nil } +// @ requires low(t) // @ decreases func HostTypeCheck(t HostAddrType) bool { switch t { diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index ed6e9032f..a91b6302b 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -23,6 +23,7 @@ import ( ) pred (h HostNone) Mem() { len(h) == HostLenNone } +pred (h HostNone) StringLow() { true } HostNone implements HostAddr @@ -30,6 +31,7 @@ pred (h HostIPv4) Mem() { len(h) == HostLenIPv4 && slices.Bytes(h, 0, len(h)) } +pred (h HostIPv4) StringLow() { true } HostIPv4 implements HostAddr @@ -37,14 +39,17 @@ pred (h HostIPv6) Mem() { len(h) == HostLenIPv6 && slices.Bytes(h, 0, len(h)) } +pred (h HostIPv6) StringLow() { true } HostIPv6 implements HostAddr pred (h HostSVC) Mem() { true } +pred (h HostSVC) StringLow() { low(h) } HostSVC implements HostAddr pred (h *HostSVC) Mem() { acc(h) } +pred (h *HostSVC) StringLow() { acc(h, _) && low(*h) } (*HostSVC) implements HostAddr diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 6eb1f6909..638da0728 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/scionproto/scion/pkg/private/serrors" + //@ sl "github.com/scionproto/scion/verification/utils/slices" ) const ( @@ -48,8 +49,10 @@ type ISD uint16 // ParseISD parses an ISD from a decimal string. Note that ISD 0 is parsed // without any errors. +// @ requires low(s) +// @ ensures low(retISD) && low(retErr) // @ decreases -func ParseISD(s string) (ISD, error) { +func ParseISD(s string) (retISD ISD, retErr error) { isd, err := strconv.ParseUint(s, 10, ISDBits) if err != nil { return 0, serrors.WrapStr("parsing ISD", err) @@ -71,13 +74,17 @@ type AS uint64 // ParseAS parses an AS from a decimal (in the case of the 32bit BGP AS number // space) or ipv6-style hex (in the case of SCION-only AS numbers) string. +// @ requires low(_as) // @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr) // @ decreases func ParseAS(_as string) (retAs AS, retErr error) { return parseAS(_as, ":") } +// @ requires low(_as) && low(sep) // @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr) // @ decreases func parseAS(_as string, sep string) (retAs AS, retErr error) { parts := strings.Split(_as, sep) @@ -87,16 +94,27 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { } if len(parts) != asParts { + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(sep) + //@ assert low(_as) + //@ ghost errCtx := []interface{}{"sep", sep, "value", _as} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("wrong number of separators", "sep", sep, "value", _as) } var parsed AS //@ invariant 0 <= i && i <= asParts - //@ invariant acc(parts) + //@ invariant forall i int :: { &parts[i] } 0 <= i && i < len(parts) ==> acc(&parts[i]) && low(parts[i]) + //@ invariant low(i) && low(_as) && low(parsed) //@ decreases asParts - i for i := 0; i < asParts; i++ { parsed <<= asPartBits v, err := strconv.ParseUint(parts[i], asPartBase, asPartBits) if err != nil { + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(i) + //@ assert low(_as) + //@ ghost errCtx := []interface{}{"index", i, "value", _as} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.WrapStr("parsing AS part", err, "index", i, "value", _as) } parsed |= AS(v) @@ -105,12 +123,19 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { // against future refactor mistakes. if !parsed.inRange() { // (VerifiedSCION) Added cast around MaxAS to be able to call serrors.New + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(uint64(MaxAS)) + //@ assert low(_as) + //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", _as} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("AS out of range", "max", uint64(MaxAS), "value", _as) } return parsed, nil } +// @ requires low(s) // @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr) // @ decreases func asParseBGP(s string) (retAs AS, retErr error) { _as, err := strconv.ParseUint(s, 10, BGPASBits) @@ -132,30 +157,43 @@ func asParseBGP(s string) (retAs AS, retErr error) { } // @ requires _as.inRange() +// @ requires low(_as) // @ decreases func (_as AS) String() string { return fmtAS(_as, ":") } +// ensures low(_as) ==> low(res) +// // @ decreases // @ pure -func (_as AS) inRange() bool { +func (_as AS) inRange() (res bool) { return _as <= MaxAS } +// @ requires low(_as) // @ decreases func (_as AS) MarshalText() ([]byte, error) { if !_as.inRange() { // (VerifiedSCION) Added cast around MaxAS and as to be able to call serrors.New + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(uint64(MaxAS)) + //@ assert low(uint64(_as)) + //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", uint64(_as)} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return nil, serrors.New("AS out of range", "max", uint64(MaxAS), "value", uint64(_as)) } return []byte(_as.String()), nil } +// @ requires sl.LowBytes(text, 0, len(text)) // @ preserves acc(_as) -// @ preserves forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) +// @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases func (_as *AS) UnmarshalText(text []byte) error { + //@ unfold sl.LowBytes(text, 0, len(text)) + // SIF: See Gobra issue #832 + //@ assume low(string(text)) parsed, err := ParseAS(string(text)) if err != nil { return err @@ -176,8 +214,10 @@ type IA uint64 // is encountered. Callers must ensure that the values passed to this function // are valid. // @ requires _as.inRange() +// @ requires low(isd) && low(_as) +// @ ensures low(res) // @ decreases -func MustIAFrom(isd ISD, _as AS) IA { +func MustIAFrom(isd ISD, _as AS) (res IA) { ia, err := IAFrom(isd, _as) if err != nil { panic(fmt.Sprintf("parsing ISD-AS: %s", err)) @@ -187,7 +227,9 @@ func MustIAFrom(isd ISD, _as AS) IA { // IAFrom creates an IA from the ISD and AS number. // @ requires _as.inRange() +// @ requires low(_as.inRange()) // @ ensures err == nil +// @ ensures low(isd) && low(_as) ==> low(ia) && low(err) // @ decreases func IAFrom(isd ISD, _as AS) (ia IA, err error) { if !_as.inRange() { @@ -197,10 +239,16 @@ func IAFrom(isd ISD, _as AS) (ia IA, err error) { } // ParseIA parses an IA from a string of the format 'isd-as'. +// @ requires low(ia) +// @ ensures low(retErr) // @ decreases -func ParseIA(ia string) (IA, error) { +func ParseIA(ia string) (retIA IA, retErr error) { parts := strings.Split(ia, "-") if len(parts) != 2 { + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(ia) + //@ ghost errCtx := []interface{}{"value", ia} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("invalid ISD-AS", "value", ia) } isd, err := ParseISD(parts[0]) @@ -214,25 +262,34 @@ func ParseIA(ia string) (IA, error) { return MustIAFrom(isd, _as), nil } +// @ requires low(ia) +// @ ensures low(res) // @ decreases -func (ia IA) ISD() ISD { +func (ia IA) ISD() (res ISD) { return ISD(ia >> ASBits) } +// @ requires low(ia) +// @ ensures low(res) // @ decreases -func (ia IA) AS() AS { +func (ia IA) AS() (res AS) { return AS(ia) & MaxAS } +// @ requires low(ia) // @ decreases func (ia IA) MarshalText() ([]byte, error) { return []byte(ia.String()), nil } +// @ requires low(len(b)) && sl.LowBytes(b, 0, len(b)) // @ preserves acc(ia) -// @ preserves forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) +// @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases func (ia *IA) UnmarshalText(b []byte) error { + //@ unfold sl.LowBytes(b, 0, len(b)) + // SIF: See Gobra issue #832 + //@ assume low(string(b)) parsed, err := ParseIA(string(b)) if err != nil { return err @@ -253,18 +310,28 @@ func (ia IA) Equal(other IA) bool { } // IsWildcard returns whether the ia has a wildcard part (isd or as). +// @ requires low(ia) // @ decreases func (ia IA) IsWildcard() bool { return ia.ISD() == 0 || ia.AS() == 0 } +// @ requires low(ia) // @ decreases func (ia IA) String() string { // (VerifiedSCION) Added casts around ia.ISD() and ia.AS() to be able to pass them to 'fmt.Sprintf' - return fmt.Sprintf("%d-%s", ia.ISD(), ia.AS()) + isd := ia.ISD() + _as := ia.AS() + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ assert low(isd) + //@ assert low(_as) + //@ ghost errCtx := []interface{}{isd, _as} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + return fmt.Sprintf("%d-%s", isd, _as) } // Set implements flag.Value interface +// @ requires low(s) // @ preserves acc(ia) // @ decreases func (ia *IA) Set(s string) error { diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 3bd12dd19..046c9ad54 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -23,6 +23,10 @@ import ( ) pred (ia *IA) Mem() { acc(ia) } +// SIF: Note that this leads to some repetition of `acc`, however we usually +// don't need `LowMem` in the postconditions, so I need something separate. +// And in other cases, we don't need any sensitivity requirements. +pred (ia *IA) LowMem() { acc(ia, _) && low(*ia) } (*IA) implements encoding.TextUnmarshaler { (ia *IA) UnmarshalText(text []byte) (err error) { @@ -35,11 +39,14 @@ pred (ia *IA) Mem() { acc(ia) } // Implementation proof would confuse the two predicates named Mem for IA and *IA // Issue: https://github.com/viperproject/gobra/issues/449 pred MemForStringer(ia IA) { true } +pred LowMemForStringer(ia IA) { low(ia) } IA implements fmt.Stringer { pred Mem := MemForStringer + pred LowMem := LowMemForStringer (ia IA) String() string { + unfold LowMemForStringer(ia) return ia.String() } } @@ -57,6 +64,7 @@ pred (_as *AS) Mem() { acc(_as) } (*IA) implements flag.Value { (ia *IA) String() (str string) { unfold ia.Mem() + unfold ia.LowMem() str = ia.String() fold ia.Mem() } diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 86f835cb4..2d8715580 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,12 +76,16 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. +// SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. +requires low(msg) && low(cause) && low(len(errCtx)) requires cause != nil ==> cause.ErrorMem() -preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) && low(errCtx[i]) +ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. // requires forall i int :: 0 <= i && i < len(errCtx) ==> IsOfPrimitiveType(errCtx[i]) ensures res != nil && res.ErrorMem() ensures cause != nil ==> (res.ErrorMem() --* cause.ErrorMem()) +ensures low(res) decreases func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index 8c5947e25..931b0a967 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -16,10 +16,15 @@ package encoding +import sl "github.com/scionproto/scion/verification/utils/slices" + type TextUnmarshaler interface { pred Mem() - preserves Mem() && acc(text) + // SIF: Needed e.g. for `(*AS), (*IA).UnmarshalText` + requires low(len(text)) && sl.LowBytes(text, 0, len(text)) + preserves Mem() + ensures acc(text) decreases UnmarshalText(text []byte) error } diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index d8c640a58..3b42afcbb 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -18,12 +18,19 @@ package flag type Value interface { pred Mem() + // SIF: For sensitivity requirements + pred LowMem() + // `LowMem` should always only contain read permissions, so no `acc` necessary + requires LowMem() preserves acc(Mem()) decreases String() string + // SIF: This is necessary e.g. for `*IA` bc. `(*IA).Set` + // uses `s` in a branch condition. + requires low(s) preserves acc(Mem()) decreases - Set(string) error + Set(s string) error } diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 6815d9ada..e83793a48 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -40,7 +40,9 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() + pred LowMem() + requires LowMem() preserves acc(Mem()) decreases String() string diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 5f552f876..c1ac64281 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -61,6 +61,7 @@ preserves forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R15) func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. +requires low(len(ip)) preserves wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) preserves !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len @@ -70,6 +71,7 @@ ensures (len(ip) == IPv6len && !(isZeros(ip[0:10]) && ip[10] == 255 && ip[11] ensures (len(ip) == IPv6len && res != nil) ==> (forall i int :: { &ip[12+i] }{ &res[i] } 0 <= i && i < IPv4len ==> &ip[12+i] == &res[i]) ensures len(ip) != IPv4len && len(ip) != IPv6len ==> res == nil +ensures low(res != nil) decreases func (ip IP) To4(ghost wildcard bool) (res IP) { if len(ip) == IPv4len { @@ -141,5 +143,6 @@ func (ip IP) Equal(x IP) bool // ParseIP parses s as an IP address, returning the result. ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len +ensures low(s) ==> low(res) decreases _ func ParseIP(s string) (res IP) diff --git a/verification/dependencies/strconv/atoi.gobra b/verification/dependencies/strconv/atoi.gobra index 26ee30282..1991d7fee 100644 --- a/verification/dependencies/strconv/atoi.gobra +++ b/verification/dependencies/strconv/atoi.gobra @@ -39,6 +39,7 @@ requires base == 0 || (2 <= base && base <= 36) requires bitSize > 0 && bitSize <= 64 ensures retErr == nil ==> (ret >= 0 && ret < Exp(2, bitSize)) ensures retErr != nil ==> retErr.ErrorMem() +ensures (low(s) && low(base) && low(bitSize)) ==> (low(ret) && low(retErr)) decreases _ func ParseUint(s string, base int, bitSize int) (ret uint64, retErr error) diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index 0b62814c6..91e972af0 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -67,6 +67,8 @@ func SplitAfterN(s, sep string, n int) (res []string) // Split slices s into all substrings separated by sep and returns a slice of // the substrings between those separators. ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) +ensures (low(s) && low(sep)) ==> low(len(res)) +ensures (low(s) && low(sep)) ==> forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) && low(res[i]) decreases _ func Split(s, sep string) (res []string) //{ return genSplit(s, sep, 0, -1) } @@ -105,6 +107,7 @@ func HasPrefix(s, prefix string) (ret bool) { // HasSuffix tests whether the string s ends with suffix. pure ensures ret == (len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix) +ensures low(s) && low(suffix) ==> low(ret) decreases func HasSuffix(s, suffix string) (ret bool) { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix @@ -161,8 +164,9 @@ func TrimPrefix(s, prefix string) string // TrimSuffix returns s without the provided trailing suffix string. // If s doesn't end with suffix, s is returned unchanged. +ensures low(s) && low(suffix) ==> low(res) decreases _ -func TrimSuffix(s, suffix string) string +func TrimSuffix(s, suffix string) (res string) // Replace returns a copy of the string s with the first n // non-overlapping instances of old replaced by new. From 9e1a702dcbf73219fa6961c081c0f8a6d0a43b28 Mon Sep 17 00:00:00 2001 From: henriman Date: Sun, 2 Feb 2025 16:01:49 +0100 Subject: [PATCH 019/104] Remove MWEs here --- verification/scratch/mwe1/mwe1.gobra | 265 --------------------------- verification/scratch/mwe2/mwe2.gobra | 136 -------------- 2 files changed, 401 deletions(-) delete mode 100644 verification/scratch/mwe1/mwe1.gobra delete mode 100644 verification/scratch/mwe2/mwe2.gobra diff --git a/verification/scratch/mwe1/mwe1.gobra b/verification/scratch/mwe1/mwe1.gobra deleted file mode 100644 index 7d94d32d7..000000000 --- a/verification/scratch/mwe1/mwe1.gobra +++ /dev/null @@ -1,265 +0,0 @@ -// Minimal working example 1. Only implements very basic structure. -// 0. The key K is already given. -// 1. We receive a message M over the network. -// 2. We compute a "MAC tag" of M using K. -// 3. We send the computed tag over the network. -package mwe1 - -////////////////////////////// I/O Actions ////////////////////////////// -// cf. running-example/policy/actions.gobra - -// Definition of I/O actions (abstract, for trace). -type Action adt { - RecvIO{int} - SendIO{int} - // NOTE: I have left out `p` (cf. paper) for now - // It would be necessary once we have multiple options of which message to - // declassify at some point in order to make this deterministic in the - // previous I/O actions again. However, at the moment, our IOD spec only - // allows declassifying one message depending on the previous I/O actions - DeclassifyIO{int} -} - -// Extract input of I/O action. 0 signifies no input. -ghost -decreases -pure func (a Action) Input() int { - return match a { - case RecvIO{?m}: m - case SendIO{_}: 0 - case DeclassifyIO{_}: 0 - } -} - -// Extract output of I/O action. 0 signifies no output. -ghost -decreases -pure func (a Action) Output() int { - return match a { - case RecvIO{_}: 0 - case SendIO{?t}: t - case DeclassifyIO{?t}: t - } -} - -////////////////////////////// Classification spec. ////////////////////////////// -// cf. running-example/policy/classification.gobra -// cf. running-example/classifications/basic.gobra - -type ClassificationSpec interface { - ghost - decreases - pure Classification(Action) Specification -} - -// Gives universal access to the trace. -// `pure` ensures the resulting pointer always points to the same trace. -ghost -decreases -pure func History() *Trace - -type Trace adt { - Empty{} - Snoc{Trace;Action} // Snoc: reverse cons -} - -type Specification adt { - Spec{Observation;Observation} -} - -type Observation adt { - Value{int} // NOTE: eventually might want to switch back to any - None{} - Some{Observation} - Tuple{Observation;Observation} -} - -type ObservationTrace adt { - EmptyObs{} - SnocObs{InitObs ObservationTrace;Observation} -} - -// The following is our assertion language. -ghost -decreases -pure func True() Observation { - return None{} -} - -ghost -decreases -pure func Low(v int) Observation { - return Value{v} -} - -// Given that all sensitivity preconditions have been satisfied in the trace, -// this allows us to assume that the sensitivity postconditions are satisfied. -ghost -decreases -requires sig != nil && acc(History(), 1/2) && low(Pre(sig,*History())) -ensures acc(History(), 1/2) && low(Post(sig,*History())) -func LowPost(sig ClassificationSpec) - -// NOTE: these are the low projections mentioned in the paper -ghost -decreases -pure func pre_(spec Specification) Observation { - return match spec { - case Spec{?p, _}: p - } -} -ghost -decreases -pure func post_(spec Specification) Observation { - return match spec { - case Spec{_, ?q}: q - } -} - -ghost -decreases len(trace) -requires sig != nil -pure func Pre(sig ClassificationSpec, trace Trace) ObservationTrace { - return match trace { - case Empty{}: EmptyObs{} - case Snoc{?t, ?e}: SnocObs{Pre(sig, t), pre_(sig.Classification(e))} - } -} - -ghost -decreases len(trace) -requires sig != nil -pure func Post(sig ClassificationSpec, trace Trace) ObservationTrace { - return match trace { - case Empty{}: EmptyObs{} - case Snoc{?t, ?e}: SnocObs{Post(sig, t), post_(sig.Classification(e))} - } -} - -type DefaultClassification struct {} - -ghost -decreases -pure func (DefaultClassification) Classification(a Action) Specification { - return match a { - case DeclassifyIO{?t}: Spec{True(), Low(t)} // Make `t` low. - case _: Spec{Low(a.Output()), Low(a.Input())} - } -} - -////////////////////////////// I/O spec. ////////////////////////////// -// cf. running-example/policy/iodspec.gobra - -// We express the IODSpec as a (IOD-)guarded transition system. -type IODSpec interface { - // `Guard` specifies which I/O actions may be taken, depending on the - // (content of) the action (in particular, not on the sensitivity). - ghost - decreases - pure Guard(state, Action) bool - - ghost - decreases - pure Update(state, Action) state -} - -// NOTE: We don't need IsTrace but it is not clear how to translate Reaches. -type Restriction domain { - func Reaches(IODSpec, Trace, state) bool - - axiom { - forall r IODSpec, t Trace, s state, a Action :: { Snoc{t, a}, Reaches(r, t, s) } Reaches(r, t, s) && r.Guard(s, a) ==> Reaches(r, Snoc{t, a}, r.Update(s, a)) - } -} - -// Our I/O spec. The state is the private key and the most recently received message. -type MWE1 struct {} - -type state struct { - key int - lastMsg int -} - -// We allow send, recv to happen at any point. -// Declassify can only be called on a MAC tag of the most recently received message -// generated with the private key. -ghost -decreases -pure func (MWE1) Guard(s state, a Action) bool { - return match a { - // NOTE: This makes our IOD spec well-formed, as what is allowed to be - // declassified is now deterministic in the previous I/O actions. - case DeclassifyIO{?t}: t == MAC(s.key, s.lastMsg) - case _: true - } -} - -ghost -decreases -pure func (MWE1) Update(s state, a Action) state { - return match a { - case RecvIO{?m}: state { key: s.key, lastMsg: m } - case _: s - } -} - -////////////////////////////// Trusted library I/O ////////////////////////////// -// cf. running-example/library/library.gobra - -// Receive message `m` over network. -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), RecvIO{m}} -func Recv() (m int) - -// Send tag `t` over network. -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), SendIO{t}} -func Send(t int) - -// Declassify tag `t`. -ghost -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), DeclassifyIO{t}} -func Declassify(t int) - - -////////////////////////////// Program ////////////////////////////// - -// Abstract function representing the computation of a MAC. -// key x message -> MAC tag -decreases -pure func MAC(int, int) int - -// Receives a message, authenticates it using a MAC, and sends the resulting tag. -// The state `s` contains the private key of this router, -// and the most recently received message. -// NOTE: it should suffice here to just require Reaches(...) after the program -// has terminated, bc. at the moment we definitely terminate and there is no way -// to violate the I/O spec. and "undo" this violation later on (-> safety property). -// TODO: in the future, we should probably check this after every I/O action instead? -// In the original example, I think this is done via the shared invariant. -preserves acc(History()) && acc(s) && low(Pre(DefaultClassification{}, *History())) && Reaches(MWE1{}, *History(), *s) -func authenticate(ghost s *state) { - // NOTE: forgetting the calls to either of `Declassify` and `LowPost` - // would result in a verification error *in the postcondition* (and not in - // the call to send), as here, compliance to the classification spec is - // not checked on every I/O call, but instead on the trace at the end of - // the function call. - - m := Recv() - LowPost(DefaultClassification{}) - ghost s.lastMsg = m - - t := MAC(s.key, m) - - /* ghost */ Declassify(t) - /* ghost */ LowPost(DefaultClassification{}) - Send(t) - // The following call to `LowPost` is the only one not needed (here), - // as we don't need the postcondition of the last I/O call to verify - // the remainder of the function. - /* ghost */ LowPost(DefaultClassification{}) -} \ No newline at end of file diff --git a/verification/scratch/mwe2/mwe2.gobra b/verification/scratch/mwe2/mwe2.gobra deleted file mode 100644 index b2844c7f0..000000000 --- a/verification/scratch/mwe2/mwe2.gobra +++ /dev/null @@ -1,136 +0,0 @@ -// Minimal working example 2. -package mwe2 - -// Used to track position in protocol. -type Place int -pred token(ghost p Place) - -pred RecvPerm(ghost p Place) - -// Returns the next place after calling `Recv` from `p`. -// Avoids using an existential quantifier. -ghost -decreases -pure func Recv_T(ghost p Place) Place - -// Used to refer to the received message. -ghost -decreases -pure func Recv_R(ghost p Place) int - -// This is how the state is/should be updated after receiving message `m`. -decreases -pure func Recv_S(s state, m int) state { - return state { key: s.key, lastMsg1: m, lastMsg2: s.lastMsg1 } -} - -requires token(p) && RecvPerm(p) -ensures token(next_p) && next_p == Recv_T(p) -ensures m == Recv_R(p) -ensures low(m) -func Recv(ghost p Place) (ghost next_p Place, m int) - -pred SendPerm(ghost p Place, t int) - -ghost -decreases -pure func Send_T(ghost p Place, t int) Place - -requires token(p) && SendPerm(p, t) -requires low(t) -ensures token(next_p) && next_p == Send_T(p, t) -func Send(ghost p Place, t int) (ghost next_p Place) - -pred DeclassifyPerm(ghost p Place, tag int, t int) - -ghost -decreases -pure func Declassify_T(ghost p Place, tag int, t int) Place - -ghost -requires token(p) && DeclassifyPerm(p, tag, t) -requires low(tag) -ensures token(next_p) && next_p == Declassify_T(p, tag, t) -ensures low(t) -decreases -func Declassify(ghost p Place, tag int, t int) (ghost next_p Place) - -// "Linear" protocol. -pred Protocol1(ghost p0 Place, key int) { - // 1. Receive a message. - RecvPerm(p0) && let p1, m := Recv_T(p0), Recv_R(p0) in - // 2. Compute MAC tag and declassify it. - let tag := MAC(key, m) in - DeclassifyPerm(p1, m, tag) && let p2 := Declassify_T(p1, m, tag) in - // 3. Send MAC tag over network. - SendPerm(p2, tag) && let p3 := Send_T(p2, tag) in - // 4. Restart. - Protocol1(p3, key) -} - -type state struct { - key int // the private key - lastMsg1 int // 1st most recently received message - lastMsg2 int // 2nd -} - -pred Protocol2(ghost p0 Place, s0 state) { - // Receive a message at any time. - RecvPerm(p0) && - let p1, s1 := Recv_T(p0), Recv_S(s0, Recv_R(p0)) in Protocol2(p1, s1) && - // NOTE: at the moment we can declassify things before receiving anything - // Declassify and send either the MAC tag of the most or the 2nd most - // recently received message. - let tag1, tag2 := MAC(s0.key, s0.lastMsg1), MAC(s0.key, s0.lastMsg2) in - - DeclassifyPerm(p0, s0.lastMsg1, tag1) && - let p1 := Declassify_T(p0, s0.lastMsg1, tag1) in Protocol2(p1, s0) && - - DeclassifyPerm(p0, s0.lastMsg2, tag2) && - let p1 := Declassify_T(p0, s0.lastMsg2, tag2) in Protocol2(p1, s0) && - - SendPerm(p0, tag1) && - let p1 := Send_T(p0, tag1) in Protocol2(p1, s0) && - - SendPerm(p0, tag2) && - let p1 := Send_T(p0, tag2) in Protocol2(p1, s0) -} - -// Abstract function representing the computation of a MAC. -// key x message -> MAC tag -decreases -pure func MAC(int, int) int - -requires token(p0) && Protocol2(p0, s) -func authenticate(ghost p0 Place, s state) { - - invariant token(p0) && Protocol2(p0, s) - for { - unfold Protocol2(p0, s) - // ghost p1 := Recv_T(p0) - p1, m1 := Recv(p0) - s = Recv_S(s, m1) - - unfold Protocol2(p1, s) - // ghost p2 := Recv_T(p1) - p2, m2 := Recv(p1) - s = Recv_S(s, m2) - - unfold Protocol2(p2, s) - // ghost p3 := Recv_T(p2) - p3, m3 := Recv(p2) - s = Recv_S(s, m3) - - // We can use m2, m3 here. m1 won't work. - t := MAC(s.key, m3) - - unfold Protocol2(p3, s) - // ghost p4 := Declassify_T(p3, m3, t) - ghost p4 := Declassify(p3, m3, t) - - unfold Protocol2(p4, s) - // ghost p0 = Send_T(p4, t) - p0 = Send(p4, t) - } - -} \ No newline at end of file From 5728d9e8e2b20032ce3f4c9fbe6315fb27a07580 Mon Sep 17 00:00:00 2001 From: henriman Date: Sun, 2 Feb 2025 23:02:13 +0100 Subject: [PATCH 020/104] Clean up and minor fixes and improvements Most notably sensitivity requirements in interfaces have been made uniform --- pkg/addr/host.go | 48 +++++++++++-------- pkg/addr/host_spec.gobra | 35 ++++++++++---- pkg/addr/isdas.go | 31 ++++++------ pkg/addr/isdas_spec.gobra | 30 ++++++++---- .../dependencies/encoding/encoding.gobra | 6 +-- verification/dependencies/flag/flag.gobra | 19 ++++---- verification/dependencies/fmt/fmt.gobra | 13 ++--- verification/dependencies/net/ip.gobra | 8 +++- verification/dependencies/strconv/atoi.gobra | 2 +- .../dependencies/strings/strings.gobra | 4 +- 10 files changed, 116 insertions(+), 80 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index b42e16c62..8dfa385f4 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -84,6 +84,7 @@ const ( type HostAddr interface { //@ pred Mem() + //@ pred LowMem() //@ preserves acc(Mem(), R13) //@ decreases @@ -108,10 +109,10 @@ type HostAddr interface { //@ decreases Copy() (res HostAddr) - // SIF: As every implementation casts `o` to its type, and casts require the - // argument to be low (at the moment at least), I think it's OK to require - // this for every implementing type. - //@ requires low(o) + // SIF: I wanted to introduce an assertion `LowEqual(HostAddr)`, but that + // led to a strange exception. As every implementation of `Equal` needs to + // cast `o` anyway, I think it's fine to assert `low(typeOf(o))` directly. + //@ requires low(typeOf(o)) //@ preserves acc(Mem(), R13) && acc(o.Mem(), R13) //@ decreases Equal(o HostAddr) bool @@ -121,10 +122,8 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer - //@ pred StringLow() - - //@ requires StringLow() - //@ preserves acc(Mem(), R13) + //@ requires acc(Mem(), R13/2) && acc(LowMem(), R13/2) + //@ ensures acc(Mem(), R13) //@ decreases String() string } @@ -164,8 +163,8 @@ func (h HostNone) Copy() (res HostAddr) { return tmp } -// SIF: The Viper encoding contains a non-low branch condition if not `low(o)` -// @ requires low(o) +// SIF: The Viper encoding contains a non-low branch condition if not `low(typeOf(o))` +// @ requires low(typeOf(o)) // @ ensures res == (typeOf(o) == type[HostNone]) // @ decreases func (h HostNone) Equal(o HostAddr) (res bool) { @@ -173,7 +172,7 @@ func (h HostNone) Equal(o HostAddr) (res bool) { return ok } -// @decreases +// @ decreases func (h HostNone) String() string { return "" } @@ -224,8 +223,7 @@ func (h HostIPv4) Copy() (res HostAddr) { return tmp } -// SIF: Does this make sense considering HostIPv4 is byte slice underneath? -// @ requires low(o) +// @ requires low(typeOf(o)) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -238,10 +236,14 @@ func (h HostIPv4) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ preserves acc(h.Mem(), R13) +// @ requires acc(h.Mem(), R13/2) && acc(h.LowMem(), R13/2) +// @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv4) String() string { - //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 + //@ assert unfolding acc(h.Mem(), R13/2) in len(h) == HostLenIPv4 + //@ unfold acc(h.Mem(), R13/2) + //@ unfold acc(h.LowMem(), R13/2) + //@ fold acc(h.Mem(), R13) //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) return h.IP().String() @@ -294,7 +296,7 @@ func (h HostIPv6) Copy() (res HostAddr) { return tmp } -// @ requires low(o) +// @ requires low(typeOf(o)) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -307,10 +309,14 @@ func (h HostIPv6) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ preserves acc(h.Mem(), R13) +// @ requires acc(h.Mem(), R13/2) && acc(h.LowMem(), R13/2) +// @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv6) String() string { - //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv6 + //@ assert unfolding acc(h.Mem(), R13/2) in len(h) == HostLenIPv6 + //@ unfold acc(h.Mem(), R13/2) + //@ unfold acc(h.LowMem(), R13/2) + //@ fold acc(h.Mem(), R13) //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) return h.IP().String() @@ -404,7 +410,7 @@ func (h HostSVC) Copy() (res HostAddr) { return h } -// @ requires low(o) +// @ requires low(typeOf(o)) // @ decreases func (h HostSVC) Equal(o HostAddr) bool { ha, ok := o.(HostSVC) @@ -423,8 +429,8 @@ func (h HostSVC) String() string { //@ assert low(name) //@ assert low(cast) //@ assert low(uint16(h)) - //@ ghost errCtx := []interface{}{name, cast, uint16(h)} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + //@ ghost v := []interface{}{name, cast, uint16(h)} + //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%v %c (0x%04x)", name, cast, uint16(h)) } diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index a91b6302b..9992cc040 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -19,19 +19,29 @@ package addr import ( "net" + . "github.com/scionproto/scion/verification/utils/definitions" "github.com/scionproto/scion/verification/utils/slices" ) -pred (h HostNone) Mem() { len(h) == HostLenNone } -pred (h HostNone) StringLow() { true } +// SIF: Asserting h.Mem() in LowMem doesn't work well. -HostNone implements HostAddr +pred (h HostNone) Mem() { len(h) == HostLenNone } +pred (h HostNone) LowMem() { true } + +HostNone implements HostAddr { + (h HostNone) String() (str string) { + unfold acc(h.Mem(), R13/2) + unfold acc(h.LowMem(), R13/2) + str = h.String() + fold acc(h.Mem(), R13) + } +} pred (h HostIPv4) Mem() { len(h) == HostLenIPv4 && slices.Bytes(h, 0, len(h)) } -pred (h HostIPv4) StringLow() { true } +pred (h HostIPv4) LowMem() { slices.Bytes(h, 0, len(h)) } HostIPv4 implements HostAddr @@ -39,17 +49,24 @@ pred (h HostIPv6) Mem() { len(h) == HostLenIPv6 && slices.Bytes(h, 0, len(h)) } -pred (h HostIPv6) StringLow() { true } +pred (h HostIPv6) LowMem() { slices.Bytes(h, 0, len(h)) } HostIPv6 implements HostAddr pred (h HostSVC) Mem() { true } -pred (h HostSVC) StringLow() { low(h) } - -HostSVC implements HostAddr +pred (h HostSVC) LowMem() { low(h) } + +HostSVC implements HostAddr { + (h HostSVC) String() (str string) { + unfold acc(h.Mem(), R13/2) + unfold acc(h.LowMem(), R13/2) + str = h.String() + fold acc(h.Mem(), R13) + } +} pred (h *HostSVC) Mem() { acc(h) } -pred (h *HostSVC) StringLow() { acc(h, _) && low(*h) } +pred (h *HostSVC) LowMem() { acc(h) && low(*h) } (*HostSVC) implements HostAddr diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 638da0728..f25b6ad97 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -163,8 +163,7 @@ func (_as AS) String() string { return fmtAS(_as, ":") } -// ensures low(_as) ==> low(res) -// +// SIF: For pure functions, we can automatically deduce low(in) ==> low(out) // @ decreases // @ pure func (_as AS) inRange() (res bool) { @@ -214,8 +213,8 @@ type IA uint64 // is encountered. Callers must ensure that the values passed to this function // are valid. // @ requires _as.inRange() -// @ requires low(isd) && low(_as) -// @ ensures low(res) +// @ requires low(_as.inRange()) +// @ ensures low(isd) && low(_as) ==> low(res) // @ decreases func MustIAFrom(isd ISD, _as AS) (res IA) { ia, err := IAFrom(isd, _as) @@ -229,7 +228,7 @@ func MustIAFrom(isd ISD, _as AS) (res IA) { // @ requires _as.inRange() // @ requires low(_as.inRange()) // @ ensures err == nil -// @ ensures low(isd) && low(_as) ==> low(ia) && low(err) +// @ ensures low(isd) && low(_as) ==> low(ia) // @ decreases func IAFrom(isd ISD, _as AS) (ia IA, err error) { if !_as.inRange() { @@ -262,16 +261,15 @@ func ParseIA(ia string) (retIA IA, retErr error) { return MustIAFrom(isd, _as), nil } -// @ requires low(ia) -// @ ensures low(res) +// SIF: pure implies low(in) ==> low(out) // @ decreases +// @ pure func (ia IA) ISD() (res ISD) { return ISD(ia >> ASBits) } -// @ requires low(ia) -// @ ensures low(res) // @ decreases +// @ pure func (ia IA) AS() (res AS) { return AS(ia) & MaxAS } @@ -310,24 +308,25 @@ func (ia IA) Equal(other IA) bool { } // IsWildcard returns whether the ia has a wildcard part (isd or as). +// SIF: Required due to short circuit evaluation. // @ requires low(ia) // @ decreases func (ia IA) IsWildcard() bool { return ia.ISD() == 0 || ia.AS() == 0 } +// SIF: ATM `Sprintf` requires all arguments to be low, regardless of +// whether we need the output to be low. // @ requires low(ia) // @ decreases func (ia IA) String() string { // (VerifiedSCION) Added casts around ia.ISD() and ia.AS() to be able to pass them to 'fmt.Sprintf' - isd := ia.ISD() - _as := ia.AS() // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ assert low(isd) - //@ assert low(_as) - //@ ghost errCtx := []interface{}{isd, _as} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - return fmt.Sprintf("%d-%s", isd, _as) + //@ ghost v := []interface{}{ia.ISD(), ia.AS()} + //@ assert low(v[0]) + //@ assert low(v[1]) + //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) + return fmt.Sprintf("%d-%s", ia.ISD(), ia.AS()) } // Set implements flag.Value interface diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 046c9ad54..cf369191a 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -20,17 +20,23 @@ import ( "fmt" "encoding" "flag" + + sl "github.com/scionproto/scion/verification/utils/slices" ) pred (ia *IA) Mem() { acc(ia) } -// SIF: Note that this leads to some repetition of `acc`, however we usually -// don't need `LowMem` in the postconditions, so I need something separate. -// And in other cases, we don't need any sensitivity requirements. -pred (ia *IA) LowMem() { acc(ia, _) && low(*ia) } + +pred (ia *IA) LowMem() { acc(ia) && low(*ia) } +pred (ia *IA) LowSet(s string) { low(s) } + +pred (*IA) LowUnmarshalText(text []byte) { + low(len(text)) && sl.LowBytes(text, 0, len(text)) +} (*IA) implements encoding.TextUnmarshaler { (ia *IA) UnmarshalText(text []byte) (err error) { unfold ia.Mem() + unfold ia.LowUnmarshalText(text) err = ia.UnmarshalText(text) fold ia.Mem() } @@ -45,17 +51,22 @@ IA implements fmt.Stringer { pred Mem := MemForStringer pred LowMem := LowMemForStringer - (ia IA) String() string { - unfold LowMemForStringer(ia) - return ia.String() + (ia IA) String() (str string) { + unfold acc(LowMemForStringer(ia), 1/2) + str = ia.String() + fold acc(MemForStringer(ia)) } } pred (_as *AS) Mem() { acc(_as) } +pred (*AS) LowUnmarshalText(text []byte) { + low(len(text)) && sl.LowBytes(text, 0, len(text)) +} (*AS) implements encoding.TextUnmarshaler { (_as *AS) UnmarshalText(text []byte) (err error) { unfold _as.Mem() + unfold _as.LowUnmarshalText(text) err = _as.UnmarshalText(text) fold _as.Mem() } @@ -63,14 +74,15 @@ pred (_as *AS) Mem() { acc(_as) } (*IA) implements flag.Value { (ia *IA) String() (str string) { - unfold ia.Mem() - unfold ia.LowMem() + unfold acc(ia.Mem(), 1/2) + unfold acc(ia.LowMem(), 1/2) str = ia.String() fold ia.Mem() } (ia *IA) Set(s string) (err error) { unfold ia.Mem() + unfold ia.LowSet(s) err = ia.Set(s) fold ia.Mem() } diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index 931b0a967..768798c02 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -16,13 +16,13 @@ package encoding -import sl "github.com/scionproto/scion/verification/utils/slices" - type TextUnmarshaler interface { pred Mem() + + pred LowUnmarshalText([]byte) // SIF: Needed e.g. for `(*AS), (*IA).UnmarshalText` - requires low(len(text)) && sl.LowBytes(text, 0, len(text)) + requires LowUnmarshalText(text) preserves Mem() ensures acc(text) decreases diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 3b42afcbb..d109dc5a8 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -18,18 +18,19 @@ package flag type Value interface { pred Mem() - // SIF: For sensitivity requirements - pred LowMem() - - // `LowMem` should always only contain read permissions, so no `acc` necessary - requires LowMem() - preserves acc(Mem()) + pred LowMem() // SIF: allows specifying sensitivity (preconditions) + + // SIF: See `test10.gobra` for why I do it this way ATM. + requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) + ensures acc(Mem()) decreases String() string - // SIF: This is necessary e.g. for `*IA` bc. `(*IA).Set` - // uses `s` in a branch condition. - requires low(s) + pred LowSet(s string) + + // SIF: This is necessary e.g. for `*IA` bc. `(*IA).Set` uses `s` in a + // branch condition. + requires LowSet(s) preserves acc(Mem()) decreases Set(s string) error diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index e83793a48..40ad35b8c 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -23,12 +23,9 @@ import . "github.com/scionproto/scion/verification/utils/definitions" // SIF: This precondition might not be complete. // - We might want to require low(len(v)) or something similar, tho // `format` should already prescribe how many values are used -// - We might also need the order of the elements to be low requires low(format) -// TODO: Maybe split preserves as now we double precondition? -// SIF: I originally introduced `LowBytes`, a version of sl.Bytes that asserted -// the elements to be low as well. However, I was not able to fold the predicate, -// as the slice is implicitly created. +// SIF: I can't use `slices.LowBytes` here, as the slice is implicitly created +// and thus I cannot fold the predicate. requires forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) && low(v[i]) ensures forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: @@ -40,10 +37,10 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() - pred LowMem() + pred LowMem() // SIF: allows specifying sensitivity (preconditions) - requires LowMem() - preserves acc(Mem()) + requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) + ensures acc(Mem()) decreases String() string } diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index c1ac64281..28147d7db 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -143,6 +143,12 @@ func (ip IP) Equal(x IP) bool // ParseIP parses s as an IP address, returning the result. ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len -ensures low(s) ==> low(res) +// SIF: Although `low(res)` would suffice to assert both of this, I don't think +// it would be appropriate here. `low(res)` asserts that the returned references +// are equal, which I don't think is sensible (every caller should get a new +// slice). +// TODO: Revisit this after talking to @jcp19 about what it means for two +// references to be equal in Viper. +ensures low(s) ==> (low(res == nil) && low(len(res))) decreases _ func ParseIP(s string) (res IP) diff --git a/verification/dependencies/strconv/atoi.gobra b/verification/dependencies/strconv/atoi.gobra index 1991d7fee..9b035db1d 100644 --- a/verification/dependencies/strconv/atoi.gobra +++ b/verification/dependencies/strconv/atoi.gobra @@ -39,7 +39,7 @@ requires base == 0 || (2 <= base && base <= 36) requires bitSize > 0 && bitSize <= 64 ensures retErr == nil ==> (ret >= 0 && ret < Exp(2, bitSize)) ensures retErr != nil ==> retErr.ErrorMem() -ensures (low(s) && low(base) && low(bitSize)) ==> (low(ret) && low(retErr)) +ensures (low(s) && low(base) && low(bitSize)) ==> (low(ret) && low(retErr)) decreases _ func ParseUint(s string, base int, bitSize int) (ret uint64, retErr error) diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index 91e972af0..7f08e28ea 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -66,9 +66,8 @@ func SplitAfterN(s, sep string, n int) (res []string) // Split slices s into all substrings separated by sep and returns a slice of // the substrings between those separators. -ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) +ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) && ((low(s) && low(sep)) ==> low(res[i])) ensures (low(s) && low(sep)) ==> low(len(res)) -ensures (low(s) && low(sep)) ==> forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) && low(res[i]) decreases _ func Split(s, sep string) (res []string) //{ return genSplit(s, sep, 0, -1) } @@ -107,7 +106,6 @@ func HasPrefix(s, prefix string) (ret bool) { // HasSuffix tests whether the string s ends with suffix. pure ensures ret == (len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix) -ensures low(s) && low(suffix) ==> low(ret) decreases func HasSuffix(s, suffix string) (ret bool) { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix From 69f6de4dc4c6c27a0337e97f2f31fb1b3286b36f Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 4 Feb 2025 13:17:37 +0100 Subject: [PATCH 021/104] Add additional postconditions to pkg/addr/fmt.go Note that the way `strings.Builder` is annotated may not be final --- pkg/addr/fmt.go | 25 ++++++++++++++----- verification/dependencies/strconv/itoa.gobra | 3 ++- .../dependencies/strings/builder.gobra | 19 ++++++++------ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index a86f617fd..df465f759 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -113,9 +113,17 @@ func FormatAS(as_ AS, opts ...FormatOption) string { // @ requires as_.inRange() // @ requires low(as_) +// SIF: Remove eventually maybe +// @ requires low(sep) +// @ ensures low(res) // SIF: turn in to low(sep) ==> low(res) eventually maybe // @ decreases -func fmtAS(as_ AS, sep string) string { +func fmtAS(as_ AS, sep string) (res string) { if !as_.inRange() { + // SIF: See Gobra issue #835 + //@ assert low(as_) + //@ assert low(MaxAS) + //@ ghost v := []interface{}{as_, MaxAS} + //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%d [Illegal AS: larger than %d]", as_, MaxAS) } // Format BGP ASes as decimal @@ -131,21 +139,26 @@ func fmtAS(as_ AS, sep string) string { var maxLen = len("ffff:ffff:ffff") var b /*@@@*/ strings.Builder // @ b.ZeroBuilderIsReadyToUse() - b.Grow(maxLen) - // @ invariant b.Mem() + b.Grow(maxLen /*@, true @*/) + // SIF: While I do think assigning low(sep) to a ghost variable would make + // sense (here), at the moment it is simply replaced by `:= true` + //@ ghost isLowB := true + //@ ghost isLowSep := low(sep) + // @ invariant acc(b.Mem(), 1/2) && acc(b.LowMem(isLowB), 1/2) // @ invariant low(i) // @ decreases asParts - i for i := 0; i < asParts; i++ { if i > 0 { - b.WriteString(sep) + b.WriteString(sep /*@, true, true @*/) } shift := uint(asPartBits * (asParts - i - 1)) // (VerifiedSCION) the following property is guaranteed by the type system, // but Gobra cannot infer it yet // @ assume 0 <= uint64(as_>>shift)&asPartMask - b.WriteString(strconv.FormatUint(uint64(as_>>shift)&asPartMask, asPartBase)) + b.WriteString(strconv.FormatUint(uint64(as_>>shift)&asPartMask, asPartBase) /*@, true, true @*/) } - return b.String() + ret := b.String( /*@ true @*/ ) + return ret } // (VerifiedSCION) revert this change when Gobra is fixed. diff --git a/verification/dependencies/strconv/itoa.gobra b/verification/dependencies/strconv/itoa.gobra index 4f478ed1a..db86ef46b 100644 --- a/verification/dependencies/strconv/itoa.gobra +++ b/verification/dependencies/strconv/itoa.gobra @@ -16,8 +16,9 @@ const fastSmalls = true // enable fast path for small integers // for digit values >= 10. requires i >= 0 requires 2 <= base && base <= 36 +ensures low(i) && low(base) ==> low(res) decreases -func FormatUint(i uint64, base int) string +func FormatUint(i uint64, base int) (res string) // FormatInt returns the string representation of i in the given base, // for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index e614b1726..021d86c5a 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -18,25 +18,30 @@ type Builder struct { } pred (b *Builder) Mem() +pred (b *Builder) LowMem(ghost isLow bool) // String returns the accumulated string. -preserves b.Mem() +preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) +ensures isLow ==> low(str) decreases _ -func (b *Builder) String() string +func (b *Builder) String(ghost isLow bool) (str string) requires 0 <= n -preserves b.Mem() +preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) decreases _ -func (b *Builder) Grow(n int) +func (b *Builder) Grow(n int, ghost isLow bool) -preserves b.Mem() +requires acc(b.Mem(), 1/2) && acc(b.LowMem(isLowB), 1/2) +requires isLowS ==> low(s) ensures err == nil +ensures acc(b.Mem(), 1/2) && acc(b.LowMem(isLowS && isLowB), 1/2) +ensures isLowS && isLowB ==> low(n) decreases _ -func (b *Builder) WriteString(s string) (n int, err error) +func (b *Builder) WriteString(s string, ghost isLowB, isLowS bool) (n int, err error) ghost requires acc(b) requires *b === Builder{} -ensures b.Mem() +ensures acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) decreases _ func (b *Builder) ZeroBuilderIsReadyToUse() \ No newline at end of file From 41c38db585e00449e0d8b380124fc9c1bfdacc13 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Feb 2025 19:04:46 +0100 Subject: [PATCH 022/104] Change sensitivity requirements of mt.Sprintf and serrors.New --- pkg/private/serrors/serrors_spec.gobra | 8 +++----- verification/dependencies/fmt/fmt.gobra | 21 +++++++-------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 86f835cb4..922e48576 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -90,12 +90,10 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. -requires low(msg) && low(len(errCtx)) -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) && low(errCtx[i]) -ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() -ensures low(res) +// SIF: TODO: capture (part of) this using a pure Boolean function +ensures (low(msg) && low(len(errCtx)) && forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i])) ==> low(res) decreases func New(msg string, errCtx ...interface{}) (res error) diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 6815d9ada..d9163af84 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -18,23 +18,16 @@ package fmt import . "github.com/scionproto/scion/verification/utils/definitions" -// SIF: I couldn't encode low(format) && forall i :: low(v[i]) ==> low(res), -// so (at least for now) this uses pre- and postcondition. -// SIF: This precondition might not be complete. -// - We might want to require low(len(v)) or something similar, tho -// `format` should already prescribe how many values are used -// - We might also need the order of the elements to be low -requires low(format) -// TODO: Maybe split preserves as now we double precondition? -// SIF: I originally introduced `LowBytes`, a version of sl.Bytes that asserted -// the elements to be low as well. However, I was not able to fold the predicate, -// as the slice is implicitly created. -requires forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) && low(v[i]) -ensures forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) +preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) -ensures low(res) +// SIF: This implication might not be sound. +// - We might want to require low(len(v)) or something similar, tho +// `format` should already prescribe how many values are used +// - We might also need the order of the elements to be low +// SIF: TODO: capture (part of) this using a pure Boolean function +ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From 3eda2f0655e5c8f9c4a6782f96ef380ba72c3979 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 13 Feb 2025 14:26:19 +0100 Subject: [PATCH 023/104] Change pkg/slayers/doc.go to prevent parsing error doc.go contains a multi-line comment which includes "package benchmarks". At least under my Windows config, this led to Gobra incorrectly parsing it as belonging to a package called `benchmarks` (instead of `slayers`). --- pkg/slayers/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/slayers/doc.go b/pkg/slayers/doc.go index d27e24f6e..59c9844bb 100644 --- a/pkg/slayers/doc.go +++ b/pkg/slayers/doc.go @@ -63,7 +63,7 @@ even branching based on layer type... it'll handle an (scn, e2e, udp) or (scn, h Note: Great care has been taken to only lazily parse the SCION header, however, HBH and E2E extensions are currently eagerly parsed (if they exist). Thus, handling packets containing these -extensions will be much slower (see the package benchmarks for reference). +extensions will be much slower (see the Package benchmarks for reference). When using the DecodingLayerParser, the extensions can be explicitly skipped by using the HopByHop/EndToEndExtnSkipper layer. The content of this Skipper-layer can be decoded into the full representation when necessary. From 82831930fed56657f5e0dca488bc8292d2f0b578 Mon Sep 17 00:00:00 2001 From: henriman Date: Fri, 14 Feb 2025 12:22:36 +0100 Subject: [PATCH 024/104] Introduce functions to express assumptions Together with the assumptions, I also moved `LowBytes` to a dedicated folder: `verification/util/sif` --- private/topology/linktype.go | 31 ++++++++++++------------ verification/utils/sif/assumptions.gobra | 11 +++++++++ verification/utils/sif/definitions.gobra | 17 +++++++++++++ verification/utils/slices/slices.gobra | 10 -------- 4 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 verification/utils/sif/assumptions.gobra create mode 100644 verification/utils/sif/definitions.gobra diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 82b777e8c..b86e21140 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -23,6 +23,7 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" //@ . "github.com/scionproto/scion/verification/utils/definitions" //@ sl "github.com/scionproto/scion/verification/utils/slices" + //@ "github.com/scionproto/scion/verification/utils/sif" ) // LinkType describes inter-AS links. @@ -45,7 +46,8 @@ const ( ) // @ requires low(l) -// SIF: I cannot assert low(res) yet bc. I can't assert (nor assume) low(err.Error()) in if-block +// SIF: If I wanted to assert `low(res)`, I would need to annotate `error.Error` +// in `builtin.gobra`. // @ decreases func (l LinkType) String() (res string) { if l == Unset { @@ -55,8 +57,8 @@ func (l LinkType) String() (res string) { if err != nil { return err.Error() } - //@ unfold sl.LowBytes(s, 0, len(s)) - //@ assume low(string(s)) + //@ unfold sif.LowBytes(s, 0, len(s)) + //@ sif.AssumeLowSliceToLowString(s, 1/1) return string(s) } @@ -71,8 +73,8 @@ func LinkTypeFromString(s string) (res LinkType) { // SIF: For now I have to make this assumption, see Gobra issue #831 // Note that we can already infer low(len(tmp)), as the lengths are related // in the Viper encoding, but not the contents. - //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sl.LowBytes(tmp, 0, len(tmp)) + //@ assume forall i int :: { &tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ fold sif.LowBytes(tmp, 0, len(tmp)) if err := l.UnmarshalText(tmp); err != nil { return Unset } @@ -81,7 +83,7 @@ func LinkTypeFromString(s string) (res LinkType) { // @ requires low(l) // @ ensures (l == Core || l == Parent || l == Child || l == Peer) == (err == nil) -// @ ensures err == nil ==> sl.LowBytes(res, 0, len(res)) +// @ ensures err == nil ==> sif.LowBytes(res, 0, len(res)) // @ ensures err != nil ==> err.ErrorMem() // SIF: To make the postconditions as precise as possible, I have not put this // assertion behind `err == nil` or `err != nil` (resp.) @@ -96,32 +98,30 @@ func (l LinkType) MarshalText() (res []byte, err error) { tmp := []byte("core") // SIF: ATM we need this assumption, see Gobra issue #831 //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sl.LowBytes(tmp, 0, len(tmp)) + //@ fold sif.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Parent: tmp := []byte("parent") //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sl.LowBytes(tmp, 0, len(tmp)) + //@ fold sif.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Child: tmp := []byte("child") //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sl.LowBytes(tmp, 0, len(tmp)) + //@ fold sif.LowBytes(tmp, 0, len(tmp)) return tmp, nil case Peer: tmp := []byte("peer") //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sl.LowBytes(tmp, 0, len(tmp)) + //@ fold sif.LowBytes(tmp, 0, len(tmp)) return tmp, nil default: return nil, serrors.New("invalid link type") } } -// SIF: For now I assume that low(len(data)) and sl.LowBytes... suffice to infer -// that string(data) is low. See Gobra issue #832 // @ requires low(len(data)) -// @ requires acc(sl.LowBytes(data, 0, len(data)), R15) +// @ requires acc(sif.LowBytes(data, 0, len(data)), R15) // @ preserves acc(l) // @ ensures acc(sl.Bytes(data, 0, len(data)), R15) // @ ensures err != nil ==> err.ErrorMem() @@ -131,10 +131,9 @@ func (l LinkType) MarshalText() (res []byte, err error) { // @ ensures low(err) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { - //@ unfold acc(sl.LowBytes(data, 0, len(data)), R15) + //@ unfold acc(sif.LowBytes(data, 0, len(data)), R15) //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R15) - // SIF: For now I have to make this assumption, see Gobra issue #832 - //@ assume low(string(data)) + //@ sif.AssumeLowSliceToLowString(data, R15) switch strings.ToLower(string(data)) { case "core": *l = Core diff --git a/verification/utils/sif/assumptions.gobra b/verification/utils/sif/assumptions.gobra new file mode 100644 index 000000000..daa742bd8 --- /dev/null +++ b/verification/utils/sif/assumptions.gobra @@ -0,0 +1,11 @@ +// +gobra + +package sif + +// SIF: See Gobra issue #832 +ghost +requires p > 0 && acc(b, p) +requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) ==> low(b[i]) +ensures acc(b, p) && low(string(b)) +decreases +func AssumeLowSliceToLowString(b []byte, p perm) diff --git a/verification/utils/sif/definitions.gobra b/verification/utils/sif/definitions.gobra new file mode 100644 index 000000000..8a045118b --- /dev/null +++ b/verification/utils/sif/definitions.gobra @@ -0,0 +1,17 @@ +// +gobra + +package sif + +// SIF: `slices.Bytes` with the addition of asserting the elements to be low. +// TODO: I might want to eventually decouple this from access permissions +// and put sensitivity in a pure Boolean function +pred LowBytes(s []byte, start int, end int) { + // start inclusive + 0 <= start && + start <= end && + // end exclusive + end <= cap(s) && + // SIF: Might be useful in the future + // low(end - start) && + forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) && low(s[i]) +} \ No newline at end of file diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index f89de81f5..8c7c4ac15 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -32,16 +32,6 @@ pred Bytes(s []byte, start int, end int) { forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) } -// SIF: `Bytes` with the addition of asserting the elements to be low. -pred LowBytes(s []byte, start int, end int) { - // start inclusive - 0 <= start && - start <= end && - // end exclusive - end <= cap(s) && - forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) && low(s[i]) -} - pure requires acc(Bytes(s, start, end), _) requires start <= i && i < end From 576461f5e171370adb30677f1823a5278f2552b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 14 Feb 2025 16:34:19 +0100 Subject: [PATCH 025/104] Delete verification/scratch/mwe2/mwe2.gobra --- verification/scratch/mwe2/mwe2.gobra | 136 --------------------------- 1 file changed, 136 deletions(-) delete mode 100644 verification/scratch/mwe2/mwe2.gobra diff --git a/verification/scratch/mwe2/mwe2.gobra b/verification/scratch/mwe2/mwe2.gobra deleted file mode 100644 index b2844c7f0..000000000 --- a/verification/scratch/mwe2/mwe2.gobra +++ /dev/null @@ -1,136 +0,0 @@ -// Minimal working example 2. -package mwe2 - -// Used to track position in protocol. -type Place int -pred token(ghost p Place) - -pred RecvPerm(ghost p Place) - -// Returns the next place after calling `Recv` from `p`. -// Avoids using an existential quantifier. -ghost -decreases -pure func Recv_T(ghost p Place) Place - -// Used to refer to the received message. -ghost -decreases -pure func Recv_R(ghost p Place) int - -// This is how the state is/should be updated after receiving message `m`. -decreases -pure func Recv_S(s state, m int) state { - return state { key: s.key, lastMsg1: m, lastMsg2: s.lastMsg1 } -} - -requires token(p) && RecvPerm(p) -ensures token(next_p) && next_p == Recv_T(p) -ensures m == Recv_R(p) -ensures low(m) -func Recv(ghost p Place) (ghost next_p Place, m int) - -pred SendPerm(ghost p Place, t int) - -ghost -decreases -pure func Send_T(ghost p Place, t int) Place - -requires token(p) && SendPerm(p, t) -requires low(t) -ensures token(next_p) && next_p == Send_T(p, t) -func Send(ghost p Place, t int) (ghost next_p Place) - -pred DeclassifyPerm(ghost p Place, tag int, t int) - -ghost -decreases -pure func Declassify_T(ghost p Place, tag int, t int) Place - -ghost -requires token(p) && DeclassifyPerm(p, tag, t) -requires low(tag) -ensures token(next_p) && next_p == Declassify_T(p, tag, t) -ensures low(t) -decreases -func Declassify(ghost p Place, tag int, t int) (ghost next_p Place) - -// "Linear" protocol. -pred Protocol1(ghost p0 Place, key int) { - // 1. Receive a message. - RecvPerm(p0) && let p1, m := Recv_T(p0), Recv_R(p0) in - // 2. Compute MAC tag and declassify it. - let tag := MAC(key, m) in - DeclassifyPerm(p1, m, tag) && let p2 := Declassify_T(p1, m, tag) in - // 3. Send MAC tag over network. - SendPerm(p2, tag) && let p3 := Send_T(p2, tag) in - // 4. Restart. - Protocol1(p3, key) -} - -type state struct { - key int // the private key - lastMsg1 int // 1st most recently received message - lastMsg2 int // 2nd -} - -pred Protocol2(ghost p0 Place, s0 state) { - // Receive a message at any time. - RecvPerm(p0) && - let p1, s1 := Recv_T(p0), Recv_S(s0, Recv_R(p0)) in Protocol2(p1, s1) && - // NOTE: at the moment we can declassify things before receiving anything - // Declassify and send either the MAC tag of the most or the 2nd most - // recently received message. - let tag1, tag2 := MAC(s0.key, s0.lastMsg1), MAC(s0.key, s0.lastMsg2) in - - DeclassifyPerm(p0, s0.lastMsg1, tag1) && - let p1 := Declassify_T(p0, s0.lastMsg1, tag1) in Protocol2(p1, s0) && - - DeclassifyPerm(p0, s0.lastMsg2, tag2) && - let p1 := Declassify_T(p0, s0.lastMsg2, tag2) in Protocol2(p1, s0) && - - SendPerm(p0, tag1) && - let p1 := Send_T(p0, tag1) in Protocol2(p1, s0) && - - SendPerm(p0, tag2) && - let p1 := Send_T(p0, tag2) in Protocol2(p1, s0) -} - -// Abstract function representing the computation of a MAC. -// key x message -> MAC tag -decreases -pure func MAC(int, int) int - -requires token(p0) && Protocol2(p0, s) -func authenticate(ghost p0 Place, s state) { - - invariant token(p0) && Protocol2(p0, s) - for { - unfold Protocol2(p0, s) - // ghost p1 := Recv_T(p0) - p1, m1 := Recv(p0) - s = Recv_S(s, m1) - - unfold Protocol2(p1, s) - // ghost p2 := Recv_T(p1) - p2, m2 := Recv(p1) - s = Recv_S(s, m2) - - unfold Protocol2(p2, s) - // ghost p3 := Recv_T(p2) - p3, m3 := Recv(p2) - s = Recv_S(s, m3) - - // We can use m2, m3 here. m1 won't work. - t := MAC(s.key, m3) - - unfold Protocol2(p3, s) - // ghost p4 := Declassify_T(p3, m3, t) - ghost p4 := Declassify(p3, m3, t) - - unfold Protocol2(p4, s) - // ghost p0 = Send_T(p4, t) - p0 = Send(p4, t) - } - -} \ No newline at end of file From 19627eb1322e14caa5d05e4878899104c4ef6a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pereira?= Date: Fri, 14 Feb 2025 16:34:42 +0100 Subject: [PATCH 026/104] Delete verification/scratch/mwe1/mwe1.gobra --- verification/scratch/mwe1/mwe1.gobra | 265 --------------------------- 1 file changed, 265 deletions(-) delete mode 100644 verification/scratch/mwe1/mwe1.gobra diff --git a/verification/scratch/mwe1/mwe1.gobra b/verification/scratch/mwe1/mwe1.gobra deleted file mode 100644 index 7d94d32d7..000000000 --- a/verification/scratch/mwe1/mwe1.gobra +++ /dev/null @@ -1,265 +0,0 @@ -// Minimal working example 1. Only implements very basic structure. -// 0. The key K is already given. -// 1. We receive a message M over the network. -// 2. We compute a "MAC tag" of M using K. -// 3. We send the computed tag over the network. -package mwe1 - -////////////////////////////// I/O Actions ////////////////////////////// -// cf. running-example/policy/actions.gobra - -// Definition of I/O actions (abstract, for trace). -type Action adt { - RecvIO{int} - SendIO{int} - // NOTE: I have left out `p` (cf. paper) for now - // It would be necessary once we have multiple options of which message to - // declassify at some point in order to make this deterministic in the - // previous I/O actions again. However, at the moment, our IOD spec only - // allows declassifying one message depending on the previous I/O actions - DeclassifyIO{int} -} - -// Extract input of I/O action. 0 signifies no input. -ghost -decreases -pure func (a Action) Input() int { - return match a { - case RecvIO{?m}: m - case SendIO{_}: 0 - case DeclassifyIO{_}: 0 - } -} - -// Extract output of I/O action. 0 signifies no output. -ghost -decreases -pure func (a Action) Output() int { - return match a { - case RecvIO{_}: 0 - case SendIO{?t}: t - case DeclassifyIO{?t}: t - } -} - -////////////////////////////// Classification spec. ////////////////////////////// -// cf. running-example/policy/classification.gobra -// cf. running-example/classifications/basic.gobra - -type ClassificationSpec interface { - ghost - decreases - pure Classification(Action) Specification -} - -// Gives universal access to the trace. -// `pure` ensures the resulting pointer always points to the same trace. -ghost -decreases -pure func History() *Trace - -type Trace adt { - Empty{} - Snoc{Trace;Action} // Snoc: reverse cons -} - -type Specification adt { - Spec{Observation;Observation} -} - -type Observation adt { - Value{int} // NOTE: eventually might want to switch back to any - None{} - Some{Observation} - Tuple{Observation;Observation} -} - -type ObservationTrace adt { - EmptyObs{} - SnocObs{InitObs ObservationTrace;Observation} -} - -// The following is our assertion language. -ghost -decreases -pure func True() Observation { - return None{} -} - -ghost -decreases -pure func Low(v int) Observation { - return Value{v} -} - -// Given that all sensitivity preconditions have been satisfied in the trace, -// this allows us to assume that the sensitivity postconditions are satisfied. -ghost -decreases -requires sig != nil && acc(History(), 1/2) && low(Pre(sig,*History())) -ensures acc(History(), 1/2) && low(Post(sig,*History())) -func LowPost(sig ClassificationSpec) - -// NOTE: these are the low projections mentioned in the paper -ghost -decreases -pure func pre_(spec Specification) Observation { - return match spec { - case Spec{?p, _}: p - } -} -ghost -decreases -pure func post_(spec Specification) Observation { - return match spec { - case Spec{_, ?q}: q - } -} - -ghost -decreases len(trace) -requires sig != nil -pure func Pre(sig ClassificationSpec, trace Trace) ObservationTrace { - return match trace { - case Empty{}: EmptyObs{} - case Snoc{?t, ?e}: SnocObs{Pre(sig, t), pre_(sig.Classification(e))} - } -} - -ghost -decreases len(trace) -requires sig != nil -pure func Post(sig ClassificationSpec, trace Trace) ObservationTrace { - return match trace { - case Empty{}: EmptyObs{} - case Snoc{?t, ?e}: SnocObs{Post(sig, t), post_(sig.Classification(e))} - } -} - -type DefaultClassification struct {} - -ghost -decreases -pure func (DefaultClassification) Classification(a Action) Specification { - return match a { - case DeclassifyIO{?t}: Spec{True(), Low(t)} // Make `t` low. - case _: Spec{Low(a.Output()), Low(a.Input())} - } -} - -////////////////////////////// I/O spec. ////////////////////////////// -// cf. running-example/policy/iodspec.gobra - -// We express the IODSpec as a (IOD-)guarded transition system. -type IODSpec interface { - // `Guard` specifies which I/O actions may be taken, depending on the - // (content of) the action (in particular, not on the sensitivity). - ghost - decreases - pure Guard(state, Action) bool - - ghost - decreases - pure Update(state, Action) state -} - -// NOTE: We don't need IsTrace but it is not clear how to translate Reaches. -type Restriction domain { - func Reaches(IODSpec, Trace, state) bool - - axiom { - forall r IODSpec, t Trace, s state, a Action :: { Snoc{t, a}, Reaches(r, t, s) } Reaches(r, t, s) && r.Guard(s, a) ==> Reaches(r, Snoc{t, a}, r.Update(s, a)) - } -} - -// Our I/O spec. The state is the private key and the most recently received message. -type MWE1 struct {} - -type state struct { - key int - lastMsg int -} - -// We allow send, recv to happen at any point. -// Declassify can only be called on a MAC tag of the most recently received message -// generated with the private key. -ghost -decreases -pure func (MWE1) Guard(s state, a Action) bool { - return match a { - // NOTE: This makes our IOD spec well-formed, as what is allowed to be - // declassified is now deterministic in the previous I/O actions. - case DeclassifyIO{?t}: t == MAC(s.key, s.lastMsg) - case _: true - } -} - -ghost -decreases -pure func (MWE1) Update(s state, a Action) state { - return match a { - case RecvIO{?m}: state { key: s.key, lastMsg: m } - case _: s - } -} - -////////////////////////////// Trusted library I/O ////////////////////////////// -// cf. running-example/library/library.gobra - -// Receive message `m` over network. -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), RecvIO{m}} -func Recv() (m int) - -// Send tag `t` over network. -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), SendIO{t}} -func Send(t int) - -// Declassify tag `t`. -ghost -decreases -requires acc(History()) -ensures acc(History()) && *History() == Snoc{old(*History()), DeclassifyIO{t}} -func Declassify(t int) - - -////////////////////////////// Program ////////////////////////////// - -// Abstract function representing the computation of a MAC. -// key x message -> MAC tag -decreases -pure func MAC(int, int) int - -// Receives a message, authenticates it using a MAC, and sends the resulting tag. -// The state `s` contains the private key of this router, -// and the most recently received message. -// NOTE: it should suffice here to just require Reaches(...) after the program -// has terminated, bc. at the moment we definitely terminate and there is no way -// to violate the I/O spec. and "undo" this violation later on (-> safety property). -// TODO: in the future, we should probably check this after every I/O action instead? -// In the original example, I think this is done via the shared invariant. -preserves acc(History()) && acc(s) && low(Pre(DefaultClassification{}, *History())) && Reaches(MWE1{}, *History(), *s) -func authenticate(ghost s *state) { - // NOTE: forgetting the calls to either of `Declassify` and `LowPost` - // would result in a verification error *in the postcondition* (and not in - // the call to send), as here, compliance to the classification spec is - // not checked on every I/O call, but instead on the trace at the end of - // the function call. - - m := Recv() - LowPost(DefaultClassification{}) - ghost s.lastMsg = m - - t := MAC(s.key, m) - - /* ghost */ Declassify(t) - /* ghost */ LowPost(DefaultClassification{}) - Send(t) - // The following call to `LowPost` is the only one not needed (here), - // as we don't need the postcondition of the last I/O call to verify - // the remainder of the function. - /* ghost */ LowPost(DefaultClassification{}) -} \ No newline at end of file From 7d185eaeee24d1866090547e4d00a3c23b5be561 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 24 Feb 2025 18:26:03 +0100 Subject: [PATCH 027/104] Improve style --- pkg/private/serrors/serrors_spec.gobra | 7 +++++-- private/topology/underlay/defs.go | 7 +------ verification/dependencies/fmt/fmt.gobra | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 922e48576..08627d5fe 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -93,7 +93,10 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() -// SIF: TODO: capture (part of) this using a pure Boolean function -ensures (low(msg) && low(len(errCtx)) && forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i])) ==> low(res) +// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` +// using a pure Boolean function. +ensures (low(msg) && low(len(errCtx)) && + forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> + low(errCtx[i])) ==> low(res) decreases func New(msg string, errCtx ...interface{}) (res error) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 98f7e7241..801c9f73f 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -47,7 +47,6 @@ const ( EndhostPort = 30041 ) -// SIF: Branch conditions (mostly) need to be `low` // @ requires low(o) // @ ensures low(res) func (o Type) String() (res string) { @@ -63,7 +62,6 @@ func (o Type) String() (res string) { } } -// SIF: Branch conditions (mostly) need to be `low` // @ requires low(s) // @ ensures low(t) && low(err) func TypeFromString(s string) (t Type, err error) { @@ -75,15 +73,13 @@ func TypeFromString(s string) (t Type, err error) { case strings.ToLower(UDPIPv46Name): return UDPIPv46, nil default: - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835 is resolved, remove this assumption. //@ ghost errCtx := []interface{}{"type", s} //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return Invalid, serrors.New("Unknown underlay type", "type", s) } } -// SIF: I am not annotating these methods (for now), as they can't be called -// from the router anyway (cf. verification/utils/definitions/definitions.gobra) // @ trusted // @ requires Uncallable() func (ot *Type) UnmarshalJSON(data []byte) error { @@ -105,7 +101,6 @@ func (ot Type) MarshalJSON() ([]byte, error) { return json.Marshal(ot.String()) } -// SIF: Branch conditions (mostly) need to be `low` // @ requires low(ot) // @ ensures low(res) func (ot Type) IsUDP() (res bool) { diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index d9163af84..8a3d6ef65 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -22,12 +22,10 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) -// SIF: This implication might not be sound. -// - We might want to require low(len(v)) or something similar, tho -// `format` should already prescribe how many values are used -// - We might also need the order of the elements to be low -// SIF: TODO: capture (part of) this using a pure Boolean function -ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) +// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `v` +// using a pure Boolean function. +ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> + low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From 20e891c7f08b68f02e74fb4283ad5412a3d43029 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 24 Feb 2025 18:59:42 +0100 Subject: [PATCH 028/104] Align pre- and postconditions --- pkg/private/serrors/serrors_spec.gobra | 2 +- private/topology/underlay/defs.go | 6 +++--- verification/dependencies/fmt/fmt.gobra | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 08627d5fe..6ab193164 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -95,7 +95,7 @@ ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` // using a pure Boolean function. -ensures (low(msg) && low(len(errCtx)) && +ensures (low(msg) && low(len(errCtx)) && forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i])) ==> low(res) decreases diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 801c9f73f..3342cf4d2 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -48,7 +48,7 @@ const ( ) // @ requires low(o) -// @ ensures low(res) +// @ ensures low(res) func (o Type) String() (res string) { switch o { case UDPIPv4: @@ -63,7 +63,7 @@ func (o Type) String() (res string) { } // @ requires low(s) -// @ ensures low(t) && low(err) +// @ ensures low(t) && low(err) func TypeFromString(s string) (t Type, err error) { switch strings.ToLower(s) { case strings.ToLower(UDPIPv4Name): @@ -102,7 +102,7 @@ func (ot Type) MarshalJSON() ([]byte, error) { } // @ requires low(ot) -// @ ensures low(res) +// @ ensures low(res) func (ot Type) IsUDP() (res bool) { switch ot { case UDPIPv4, UDPIPv6, UDPIPv46: diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 8a3d6ef65..417568062 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -24,7 +24,7 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `v` // using a pure Boolean function. -ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> +ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From 085f827f2f5f745385d5e0a9af61933ecea5ac8d Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 24 Feb 2025 23:07:47 +0100 Subject: [PATCH 029/104] Adjust to sif.LowBytes --- pkg/addr/isdas.go | 10 +++++----- pkg/addr/isdas_spec.gobra | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index f25b6ad97..107b36ae9 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/scionproto/scion/pkg/private/serrors" - //@ sl "github.com/scionproto/scion/verification/utils/slices" + //@ "github.com/scionproto/scion/verification/utils/sif" ) const ( @@ -185,12 +185,12 @@ func (_as AS) MarshalText() ([]byte, error) { return []byte(_as.String()), nil } -// @ requires sl.LowBytes(text, 0, len(text)) +// @ requires sif.LowBytes(text, 0, len(text)) // @ preserves acc(_as) // @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases func (_as *AS) UnmarshalText(text []byte) error { - //@ unfold sl.LowBytes(text, 0, len(text)) + //@ unfold sif.LowBytes(text, 0, len(text)) // SIF: See Gobra issue #832 //@ assume low(string(text)) parsed, err := ParseAS(string(text)) @@ -280,12 +280,12 @@ func (ia IA) MarshalText() ([]byte, error) { return []byte(ia.String()), nil } -// @ requires low(len(b)) && sl.LowBytes(b, 0, len(b)) +// @ requires low(len(b)) && sif.LowBytes(b, 0, len(b)) // @ preserves acc(ia) // @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases func (ia *IA) UnmarshalText(b []byte) error { - //@ unfold sl.LowBytes(b, 0, len(b)) + //@ unfold sif.LowBytes(b, 0, len(b)) // SIF: See Gobra issue #832 //@ assume low(string(b)) parsed, err := ParseIA(string(b)) diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index cf369191a..7458a9ccf 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -21,7 +21,7 @@ import ( "encoding" "flag" - sl "github.com/scionproto/scion/verification/utils/slices" + "github.com/scionproto/scion/verification/utils/sif" ) pred (ia *IA) Mem() { acc(ia) } @@ -30,7 +30,7 @@ pred (ia *IA) LowMem() { acc(ia) && low(*ia) } pred (ia *IA) LowSet(s string) { low(s) } pred (*IA) LowUnmarshalText(text []byte) { - low(len(text)) && sl.LowBytes(text, 0, len(text)) + low(len(text)) && sif.LowBytes(text, 0, len(text)) } (*IA) implements encoding.TextUnmarshaler { @@ -60,7 +60,7 @@ IA implements fmt.Stringer { pred (_as *AS) Mem() { acc(_as) } pred (*AS) LowUnmarshalText(text []byte) { - low(len(text)) && sl.LowBytes(text, 0, len(text)) + low(len(text)) && sif.LowBytes(text, 0, len(text)) } (*AS) implements encoding.TextUnmarshaler { From 6366aa7f35fc4db2b917caa7d7c4a7ba4bb7a590 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 27 Feb 2025 12:39:47 +0100 Subject: [PATCH 030/104] Fix typo --- router/dataplane.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/dataplane.go b/router/dataplane.go index 2026a7405..3a75abb97 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -151,7 +151,7 @@ type BatchConn interface { // And also used MultiReadBioIOI_val instead of MsgToAbsVal to match what is // used in permissions. // @ ensures err == nil ==> - // @ forall i int :: { MutliReadBioIO_val(place, n)[i] } 0 <= i && i < n ==> + // @ forall i int :: { MultiReadBioIO_val(place, n)[i] } 0 <= i && i < n ==> // @ low(old(MultiReadBioIO_val(place, n)[i])) ReadBatch(msgs underlayconn.Messages /*@, ghost ingressID uint16, ghost prophecyM int, ghost place io.Place @*/) (n int, err error) // @ requires acc(addr.Mem(), _) From 005ed12914d360b3f8a1f2792027ab0590aa0162 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 3 Mar 2025 22:28:56 +0100 Subject: [PATCH 031/104] Verify private/underlay/conn --- pkg/private/serrors/serrors_spec.gobra | 7 +- private/underlay/conn/conn.go | 175 ++++++++++++++++---- private/underlay/conn/conn_spec.gobra | 63 +++++-- private/underlay/sockctrl/sockopt.go | 18 +- verification/dependencies/net/udpsock.gobra | 31 +++- 5 files changed, 235 insertions(+), 59 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 023713b63..14f4a129f 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,16 +76,13 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. -requires low(msg) && low(cause) && low(len(errCtx)) requires cause != nil ==> cause.ErrorMem() -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) && low(errCtx[i]) -ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. // requires forall i int :: 0 <= i && i < len(errCtx) ==> IsOfPrimitiveType(errCtx[i]) ensures res != nil && res.ErrorMem() ensures cause != nil ==> (res.ErrorMem() --* cause.ErrorMem()) -ensures low(res) +ensures (low(msg) && low(cause) && low(len(errCtx)) && forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i])) ==> low(res) decreases func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index ad9611602..ab254394b 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -34,6 +34,7 @@ import ( "github.com/scionproto/scion/private/underlay/sockctrl" //@ . "github.com/scionproto/scion/verification/utils/definitions" //@ sl "github.com/scionproto/scion/verification/utils/slices" + //@ "github.com/scionproto/scion/verification/utils/sif" ) // Messages is a list of ipX.Messages. It is necessary to hide the type alias @@ -43,6 +44,7 @@ type Messages []ipv4.Message // Conn describes the API for an underlay socket type Conn interface { //@ pred Mem() + // @ pred Low() // (VerifiedSCION) Reads a message to b. Returns the number of read bytes. //@ requires acc(Mem(), _) //@ preserves sl.Bytes(b, 0, len(b)) @@ -61,7 +63,7 @@ type Conn interface { //@ ensures err != nil ==> err.ErrorMem() Write(b []byte) (n int, err error) //@ requires acc(u.Mem(), _) - //@ requires acc(Mem(), _) + //@ requires acc(Mem(), _) && acc(Low(), _) //@ preserves acc(sl.Bytes(b, 0, len(b)), R10) //@ ensures err == nil ==> 0 <= n && n <= len(b) //@ ensures err != nil ==> err.ErrorMem() @@ -91,7 +93,7 @@ type Conn interface { //@ ensures err != nil ==> err.ErrorMem() //@ decreases SetDeadline(time.Time) (err error) - //@ requires Mem() + // @ requires acc(Mem(), 1/2) && acc(Low(), 1/2) //@ ensures err != nil ==> err.ErrorMem() //@ decreases Close() (err error) @@ -110,10 +112,11 @@ type Config struct { // New opens a new underlay socket on the specified addresses. // // The config can be used to customize socket behavior. -// @ requires cfg.Mem() +// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) // @ requires listen != nil || remote != nil -// @ requires listen != nil ==> acc(listen.Mem(), R10) -// @ requires remote != nil ==> acc(remote.Mem(), R10) +// @ requires listen != nil ==> acc(listen.Mem(), R10/2) && acc(listen.Low(), R10/2) +// @ requires remote != nil ==> acc(remote.Mem(), R10/2) && acc(remote.Low(), R10/2) +// @ requires low(listen) && low(remote) // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -127,8 +130,10 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { } // @ assert remote != nil ==> a == remote // @ assert remote == nil ==> a == listen - // @ unfold acc(a.Mem(), R15) - // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15) + // @ unfold acc(a.Mem(), R15/2) + // @ unfold acc(a.Low(), R15/2) + // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15/2) + // @ unfold acc(sif.LowBytes(a.IP, 0, len(a.IP)), R15/2) // @ assert forall i int :: { &a.IP[i] } 0 <= i && i < len(a.IP) ==> acc(&a.IP[i], R15) if a.IP.To4( /*@ false @*/ ) != nil { return newConnUDPIPv4(listen, remote, cfg) @@ -141,9 +146,10 @@ type connUDPIPv4 struct { pconn *ipv4.PacketConn } -// @ requires cfg.Mem() -// @ requires listen != nil ==> acc(listen.Mem(), _) -// @ requires remote != nil ==> acc(remote.Mem(), _) +// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) +// @ requires listen != nil ==> acc(listen.Mem(), _) && acc(listen.Low(), _) +// @ requires remote != nil ==> acc(remote.Mem(), _) && acc(remote.Low(), _) +// @ requires low(listen) && low(remote) // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -215,9 +221,10 @@ type connUDPIPv6 struct { pconn *ipv6.PacketConn } -// @ requires cfg.Mem() -// @ requires listen != nil ==> acc(listen.Mem(), _) -// @ requires remote != nil ==> acc(remote.Mem(), _) +// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) +// @ requires listen != nil ==> acc(listen.Mem(), _) && acc(listen.Low(), _) +// @ requires remote != nil ==> acc(remote.Mem(), _) && acc(remote.Low(), _) +// @ requires low(listen) && low(remote) // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -292,53 +299,119 @@ type connUDPBase struct { } // @ requires acc(cc) -// @ requires laddr != nil ==> acc(laddr.Mem(), _) -// @ requires raddr != nil ==> acc(raddr.Mem(), _) -// @ requires cfg.Mem() +// @ requires laddr != nil ==> acc(laddr.Mem(), _) && acc(laddr.Low(), _) +// @ requires raddr != nil ==> acc(raddr.Mem(), _) && acc(raddr.Low(), _) +// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) +// @ requires low(network) && low(laddr) && low(raddr) // @ ensures errRet == nil ==> cc.Mem() // @ ensures errRet != nil ==> errRet.ErrorMem() +// @ ensures low(errRet) // @ decreases func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cfg *Config) (errRet error) { var c *net.UDPConn var err error if laddr == nil { - return serrors.New("listen address must be specified") + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.New("listen address must be specified") + // @ assert low(reterr) + return reterr + // return serrors.New("listen address must be specified") } if raddr == nil { if c, err = net.ListenUDP(network, laddr); err != nil { - return serrors.WrapStr("Error listening on socket", err, + // @ assert low(err) + // @ assert low(network) + // @ assert low(laddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"network", network, "listen", laddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error listening on socket", err, "network", network, "listen", laddr) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error listening on socket", err, + // "network", network, "listen", laddr) } } else { if c, err = net.DialUDP(network, laddr, raddr); err != nil { - return serrors.WrapStr("Error setting up connection", err, + // @ assert low(err) + // @ assert low(network) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"network", network, "listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error setting up connection", err, "network", network, "listen", laddr, "remote", raddr) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error setting up connection", err, + // "network", network, "listen", laddr, "remote", raddr) } } - //@ unfold cfg.Mem() + //@ unfold acc(cfg.Mem(), 1/2) + // @ unfold acc(cfg.Low(), 1/2) // Set and confirm send buffer size if cfg.SendBufferSize != 0 { beforeV, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_SNDBUF) if err != nil { - return serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, + // "listen", laddr, + // "remote", raddr, + // ) } target := cfg.SendBufferSize if err = c.SetWriteBuffer(target); err != nil { - return serrors.WrapStr("Error setting send buffer size", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error setting send buffer size", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error setting send buffer size", err, + // "listen", laddr, + // "remote", raddr, + // ) } after, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_SNDBUF) if err != nil { - return serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, + // "listen", laddr, + // "remote", raddr, + // ) } if after/2 < target { // Note: kernel doubles value passed in SetSendBuffer, value @@ -355,24 +428,60 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf { beforeV, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_RCVBUF) if err != nil { - return serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, + // "listen", laddr, + // "remote", raddr, + // ) } target := cfg.ReceiveBufferSize if err = c.SetReadBuffer(target); err != nil { - return serrors.WrapStr("Error setting recv buffer size", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error setting recv buffer size", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error setting recv buffer size", err, + // "listen", laddr, + // "remote", raddr, + // ) } after, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_RCVBUF) if err != nil { - return serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, + // @ assert low(err) + // @ assert low(laddr) + // @ assert low(raddr) + // SIF: See Gobra issue #835 for why this assumption is currently necessary + //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} + //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + reterr := serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, "listen", laddr, "remote", raddr, ) + // @ assert low(reterr) + return reterr + // return serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, + // "listen", laddr, + // "remote", raddr, + // ) } if after/2 < target { // Note: kernel doubles value passed in SetReadBuffer, value @@ -385,6 +494,10 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf } } + // @ unfold acc(c.Mem(), 1/2) + // @ unfold acc(c.Low(), 1/2) + // @ fold c.Mem() + cc.conn = c cc.Listen = laddr cc.Remote = raddr @@ -414,13 +527,14 @@ func (c *connUDPBase) Write(b []byte /*@, ghost underlyingConn *net.UDPConn @*/) } // @ requires acc(dst.Mem(), _) -// @ preserves acc(c.Mem(), _) +// @ preserves acc(c.Mem(), _) && acc(c.Low(), _) // @ preserves unfolding acc(c.Mem(), _) in c.conn == underlyingConn // @ preserves acc(sl.Bytes(b, 0, len(b)), R15) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() func (c *connUDPBase) WriteTo(b []byte, dst *net.UDPAddr /*@, ghost underlyingConn *net.UDPConn @*/) (n int, err error) { //@ unfold acc(c.Mem(), _) + //@ unfold acc(c.Low(), _) if c.Remote != nil { return c.conn.Write(b) } @@ -445,11 +559,12 @@ func (c *connUDPBase) RemoteAddr() (u *net.UDPAddr) { return c.Remote } -// @ requires c.Mem() +// @ requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) // @ ensures err != nil ==> err.ErrorMem() // @ decreases func (c *connUDPBase) Close() (err error) { - //@ unfold c.Mem() + //@ unfold acc(c.Mem(), 1/2) + // @ unfold acc(c.Low(), 1/2) if c.closed { return nil } @@ -460,11 +575,13 @@ func (c *connUDPBase) Close() (err error) { // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. // @ requires 0 < n +// @ requires low(n) // @ ensures len(res) == n // @ ensures forall i int :: { &res[i] } 0 <= i && i < n ==> res[i].Mem() && res[i].GetAddr() == nil // @ decreases func NewReadMessages(n int) (res Messages) { m := make(Messages, n) + // @ invariant low(i0) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() && m[j].GetAddr() == nil //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) && m[j].N == 0 //@ invariant forall j int :: { m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index 92fcef78f..aced0f91d 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -34,12 +34,27 @@ pred (c *connUDPBase) Mem() { (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } +pred (c *connUDPBase) Low() { + acc(c) && low(c.closed) && + low(c.Remote) && + c.conn.Mem() && + (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && + (c.Remote != nil ==> acc(c.Remote.Mem(), _)) +} + pred (c *connUDPBase) MemWithoutConn() { acc(c) && (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } +pred (c *connUDPBase) LowWithoutConn() { + acc(c) && low(c.closed) && + low(c.Remote) && + (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && + (c.Remote != nil ==> acc(c.Remote.Mem(), _)) +} + // Shown to be satisfiable in newConnUDPIPv4 pred (c *connUDPIPv4) Mem() { acc(&c.pconn) && @@ -48,6 +63,12 @@ pred (c *connUDPIPv4) Mem() { c.pconn.GetUnderlyingConn() == (unfolding c.connUDPBase.MemWithoutConn() in c.conn) } +pred (c *connUDPIPv4) Low() { + acc(&c.pconn) && + c.pconn.Mem() && + c.connUDPBase.LowWithoutConn() +} + // Shown to be satisfiable in newConnUDPIPv6 pred (c *connUDPIPv6) Mem() { acc(&c.pconn) && @@ -56,12 +77,22 @@ pred (c *connUDPIPv6) Mem() { c.pconn.GetUnderlyingConn() == (unfolding c.connUDPBase.MemWithoutConn() in c.conn) } +pred (c *connUDPIPv6) Low() { + acc(&c.pconn) && + c.pconn.Mem() && + c.connUDPBase.LowWithoutConn() +} + pred (c *Config) Mem() { acc(c) && 0 <= c.SendBufferSize && 0 <= c.ReceiveBufferSize } +pred (c *Config) Low() { + acc(c) && low(*c) +} + /** Lift methods in *connUDPBase to *connUDPIPv4 **/ *connUDPIPv4 implements Conn @@ -100,19 +131,22 @@ func (c *connUDPIPv4) Write(b []byte) (n int, err error) { } requires acc(dst.Mem(), _) -preserves acc(c.Mem(), _) +preserves acc(c.Mem(), _) && acc(c.Low(), _) preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv4) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { unfold acc(c.Mem(), _) unfold acc(c.connUDPBase.MemWithoutConn(), _) + unfold acc(c.Low(), _) + unfold acc(c.connUDPBase.LowWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn tmpItf := c.pconn.ExchangeWildcardPerm() var packetconn *ipv4.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) + fold acc(c.connUDPBase.Low(), _) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -135,14 +169,17 @@ func (c *connUDPIPv4) RemoteAddr() (u *net.UDPAddr) { return c.connUDPBase.RemoteAddr() } -requires c.Mem() +requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv4) Close() (err error) { - unfold c.Mem() - unfold c.connUDPBase.MemWithoutConn() + unfold acc(c.Mem(), 1/2) + unfold acc(c.connUDPBase.MemWithoutConn(), 1/2) + unfold acc(c.Low(), 1/2) + unfold acc(c.connUDPBase.LowWithoutConn(), 1/2) c.pconn.ExchangePerm() - fold c.connUDPBase.Mem() + fold acc(c.connUDPBase.Mem(), 1/2) + fold acc(c.connUDPBase.Low(), 1/2) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv4 **/ @@ -186,19 +223,22 @@ func (c *connUDPIPv6) Write(b []byte) (n int, err error) { } requires acc(dst.Mem(), _) -preserves acc(c.Mem(), _) +preserves acc(c.Mem(), _) && acc(c.Low(), _) preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv6) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { unfold acc(c.Mem(), _) unfold acc(c.connUDPBase.MemWithoutConn(), _) + unfold acc(c.Low(), _) + unfold acc(c.connUDPBase.LowWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn tmpItf := c.pconn.ExchangeWildcardPerm() var packetconn *ipv6.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) + fold acc(c.connUDPBase.Low(), _) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -221,14 +261,17 @@ func (c *connUDPIPv6) RemoteAddr() (u *net.UDPAddr) { return c.connUDPBase.RemoteAddr() } -requires c.Mem() +requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv6) Close() (err error) { - unfold c.Mem() - unfold c.connUDPBase.MemWithoutConn() + unfold acc(c.Mem(), 1/2) + unfold acc(c.connUDPBase.MemWithoutConn(), 1/2) + unfold acc(c.Low(), 1/2) + unfold acc(c.connUDPBase.LowWithoutConn(), 1/2) c.pconn.ExchangePerm() - fold c.connUDPBase.Mem() + fold acc(c.connUDPBase.Mem(), 1/2) + fold acc(c.connUDPBase.Low(), 1/2) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv6 **/ diff --git a/private/underlay/sockctrl/sockopt.go b/private/underlay/sockctrl/sockopt.go index 3fdf7295a..6cb0ec0b5 100644 --- a/private/underlay/sockctrl/sockopt.go +++ b/private/underlay/sockctrl/sockopt.go @@ -21,10 +21,12 @@ import ( "syscall" ) -//@ trusted -//@ preserves c.Mem() -//@ ensures e != nil ==> e.ErrorMem() -//@ decreases _ +// @ trusted +// @ requires low(level) && low(opt) +// @ preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +// @ ensures e != nil ==> e.ErrorMem() +// @ ensures low(r) && low(e) +// @ decreases _ func GetsockoptInt(c *net.UDPConn, level, opt int) (r int, e error) { var val int err := SockControl(c, func(fd int) error { @@ -35,10 +37,10 @@ func GetsockoptInt(c *net.UDPConn, level, opt int) (r int, e error) { return val, err } -//@ trusted -//@ preserves c.Mem() -//@ ensures e != nil ==> e.ErrorMem() -//@ decreases _ +// @ trusted +// @ preserves c.Mem() +// @ ensures e != nil ==> e.ErrorMem() +// @ decreases _ func SetsockoptInt(c *net.UDPConn, level, opt, value int) (e error) { return SockControl(c, func(fd int) error { return syscall.SetsockoptInt(fd, level, opt, value) diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index d1a9f2b71..5bed7afca 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -13,6 +13,7 @@ import "time" import . "github.com/scionproto/scion/verification/utils/definitions" import sl "github.com/scionproto/scion/verification/utils/slices" +import "github.com/scionproto/scion/verification/utils/sif" // UDPAddr represents the address of a UDP end point. type UDPAddr struct { @@ -27,6 +28,10 @@ pred (a *UDPAddr) Mem() { acc(a, R5) && sl.Bytes(a.IP, 0, len(a.IP)) } +pred (a *UDPAddr) Low() { + acc(a, R5) && low(len(a.IP)) && sif.LowBytes(a.IP, 0, len(a.IP)) +} + (*UDPAddr) implements Addr { (e *UDPAddr) Network() string { return e.Network() @@ -56,6 +61,10 @@ pred (u *UDPConn) Mem() { acc(u) } +pred (u *UDPConn) Low() { + acc(u) && low(*u) +} + // ReadFromUDP acts like ReadFrom but returns a UDPAddr. preserves acc(c.Mem(), _) preserves sl.Bytes(b, 0, len(b)) @@ -112,25 +121,33 @@ ensures err != nil ==> err.ErrorMem() decreases func (c *UDPConn) Close() (err error) -requires acc(laddr.Mem(), _) -ensures err == nil ==> conn.Mem() +requires acc(laddr.Mem(), _) && acc(laddr.Low(), _) +requires low(network) +ensures err == nil ==> acc(conn.Mem(), 1/2) && acc(conn.Low(), 1/2) ensures err != nil ==> err.ErrorMem() +ensures low(err) decreases _ func ListenUDP(network string, laddr *UDPAddr) (conn *UDPConn, err error) -requires acc(laddr.Mem(), _) -requires acc(raddr.Mem(), _) -ensures err == nil ==> conn.Mem() +requires low(network) +requires acc(laddr.Mem(), _) && acc(laddr.Low(), _) +requires acc(raddr.Mem(), _) && acc(raddr.Low(), _) +ensures err == nil ==> acc(conn.Mem(), 1/2) && acc(conn.Low(), 1/2) ensures err != nil ==> err.ErrorMem() +ensures low(err) decreases _ func DialUDP(network string, laddr, raddr *UDPAddr) (conn *UDPConn, err error) -preserves c.Mem() +requires low(bytes) +preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) ensures err != nil ==> err.ErrorMem() +ensures low(err) decreases _ func (c *UDPConn) SetWriteBuffer(bytes int) (err error) -preserves c.Mem() +requires low(bytes) +preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +ensures low(err) ensures err != nil ==> err.ErrorMem() decreases _ func (c *UDPConn) SetReadBuffer(bytes int) (err error) From 8e17e2737504e10bab4fed1bdbb665460fd0d65d Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 5 Mar 2025 23:30:30 +0100 Subject: [PATCH 032/104] Verify pkg/slayers/path/empty except for implementation proof --- pkg/slayers/path/empty/empty.go | 1 + pkg/slayers/path/path.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/slayers/path/empty/empty.go b/pkg/slayers/path/empty/empty.go index 73e92dc15..82bf48e00 100644 --- a/pkg/slayers/path/empty/empty.go +++ b/pkg/slayers/path/empty/empty.go @@ -57,6 +57,7 @@ func RegisterPath() { // bytes on the wire and is used for AS internal communication. type Path struct{} +// @ requires low(len(r)) // @ ensures len(r) == 0 ==> (e == nil && o.Mem(r)) // @ ensures len(r) != 0 ==> (e != nil && e.ErrorMem() && o.NonInitMem()) // @ decreases diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 9643e4aef..76d175d87 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -80,6 +80,7 @@ type Path interface { // (VerifiedSCION) There are implementations of this interface (e.g., scion.Raw) that // store b and use it as internal data. //@ requires NonInitMem() + //@ requires low(len(b)) //@ preserves acc(sl.Bytes(b, 0, len(b)), R42) //@ ensures err == nil ==> Mem(b) //@ ensures err == nil ==> IsValidResultOfDecoding(b) From a3534685af5380081c83832e602c68145831c688 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 10 Mar 2025 17:42:00 +0100 Subject: [PATCH 033/104] Address PR feedback and clean up --- pkg/slayers/scion_spec.gobra | 2 +- router/dataplane.go | 24 ++++++++-------- router/io-spec-atomic-events.gobra | 9 ++---- verification/io/io-spec.gobra | 45 +++++++++++++----------------- verification/io/values.gobra | 10 +++---- 5 files changed, 41 insertions(+), 49 deletions(-) diff --git a/pkg/slayers/scion_spec.gobra b/pkg/slayers/scion_spec.gobra index 1b45a2896..e45ae6064 100644 --- a/pkg/slayers/scion_spec.gobra +++ b/pkg/slayers/scion_spec.gobra @@ -441,7 +441,7 @@ pure func (s *SCION) ValidScionInitSpec(ub []byte) bool { let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := s.HdrLen*LineLen in typeOf(s.Path) == (*scion.Raw) && - s.Path.(*scion.Raw).GetBase(ub[low__:high]).WeaklyValid() + s.Path.(*scion.Raw).GetBase(ub[low_:high]).WeaklyValid() } // Checks if the common path header is valid in the serialized scion packet. diff --git a/router/dataplane.go b/router/dataplane.go index 3a75abb97..21e13a0b9 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -145,12 +145,14 @@ type BatchConn interface { // @ ensures err == nil ==> // @ forall i int :: { &msgs[i] } 0 <= i && i < n ==> // @ MsgToAbsVal(&msgs[i], ingressID) == old(MultiReadBioIO_val(place, n)[i]) - // SIF: classification spec - // NOTE: The postcondition for recv specifies that received messages are low. - // TODO: I have decided to mark the abstraction as low for now. - // And also used MultiReadBioIOI_val instead of MsgToAbsVal to match what is - // used in permissions. - // @ ensures err == nil ==> + // Classification spec for SIF. + // The received messages are low, i.e., observable by the attacker. + // TODO: Once Gobra issue 846 is resolved, mark `msgs` as low instead of the + // abstraction, as `msgs` contains the data actually received and we don't + // necessarily have low(abstraction) ==> low(concrete). + // Expressing this without pure Boolean functions is rather tedious and not + // worth the time at the moment, IMO. + // @ ensures err == nil ==> // @ forall i int :: { MultiReadBioIO_val(place, n)[i] } 0 <= i && i < n ==> // @ low(old(MultiReadBioIO_val(place, n)[i])) ReadBatch(msgs underlayconn.Messages /*@, ghost ingressID uint16, ghost prophecyM int, ghost place io.Place @*/) (n int, err error) @@ -159,7 +161,6 @@ type BatchConn interface { // @ preserves acc(sl.Bytes(b, 0, len(b)), R10) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() - // SIF: add to classification spec later as well (bfdSend) WriteTo(b []byte, addr *net.UDPAddr) (n int, err error) // @ requires acc(Mem(), _) // (VerifiedSCION) opted for less reusable spec for WriteBatch for @@ -170,10 +171,11 @@ type BatchConn interface { // preconditions for IO-spec: // @ requires MsgToAbsVal(&msgs[0], egressID) == ioAbsPkts // @ requires io.token(place) && io.CBioIO_bio3s_send(place, ioAbsPkts) - // SIF: classification spec - // NOTE: I mark `ioAbsPkts` instead of `MsgToAbsVal(...)` as low here to - // match variable used in send permission. - // @ requires low(ioAbsPkts) + // Classification spec for SIF. + // The messages to be sent need to be low, i.e., observable by the attacker. + // TODO: Once Gobra issue 846 is resolved, mark `msgs` as low instead of the + // abstraction. See comment on `ReadBatch` above for explanation. + // @ requires low(ioAbsPkts) // @ ensures acc(msgs[0].Mem(), R50) && msgs[0].HasActiveAddr() // @ ensures acc(sl.Bytes(msgs[0].GetFstBuffer(), 0, len(msgs[0].GetFstBuffer())), R50) // @ ensures err == nil ==> 0 <= n && n <= len(msgs) diff --git a/router/io-spec-atomic-events.gobra b/router/io-spec-atomic-events.gobra index efd8f140c..952b49b31 100644 --- a/router/io-spec-atomic-events.gobra +++ b/router/io-spec-atomic-events.gobra @@ -160,14 +160,11 @@ func AtomicXover(oldPkt io.IO_pkt2, ingressID option[io.IO_ifs], newPkt io.IO_pk } ghost -// NOTE: I don't think I need this here -// requires dp.Valid() -requires low(beta) && low(ts) && low(inif) && low(egif) -requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) +requires low(beta) && low(ts) && low(inif) && low(egif) +requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) preserves acc(ioLock.LockP(), _) preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> -ensures low(sigma) -// NOTE: Seems to be needed bc. lock might not terminate +ensures low(sigma) decreases _ func AtomicDecl(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { ghost ioLock.Lock() diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index 7619bc042..cca2684c7 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -34,8 +34,7 @@ pred (dp DataPlaneSpec) dp3s_iospec_ordered(s IO_dp3s_state_local, t Place) { dp.dp3s_iospec_bio3s_recv(s, t) && dp.dp3s_iospec_skip(s, t) && dp.dp3s_iospec_stop(s, t) && - // SIF: - dp.dp4s_iospec_bio4s_decl(s, t) + dp.dp4s_iospec_bio4s_decl(s, t) // SIF } type Place int @@ -301,45 +300,39 @@ pred (dp DataPlaneSpec) dp3s_iospec_stop(s IO_dp3s_state_local, t Place) { true } -/* SIF: Declassification specification. -I use "4s" to distinguish additions of the refined event system. */ +// Declassification specification for SIF. +// Members whose id start with "dp4s" correspond to additions to the IO spec +// related to SIF. pred CBio_IN_bio4s_decl(t Place, v IO_val) -// SIF: Return next place -ghost -// SIF: I left out `requires CBio_IN_bio4s_decl(t, v)` as we deem it not necessary. +ghost decreases pure func dp4s_iospec_bio4s_decl_T(t Place, v IO_val) Place ghost requires v.isIO_decl_val -// NOTE: Not sure I need this here -// requires dp.Valid() decreases pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, t Place, v IO_val) bool { - // SIF: cf. `hf_valid` in `router.gobra` - // NOTE: For some reason, formatting this differently leads to a parsing error. + // Cf. `router.gobra`, in particular `hf_valid`. + // NOTE: Formatting this differently leads to a parsing error. return v.IO_decl_val_sigma == (nextMsgtermSpec( - dp.Asid(), - v.IO_decl_val_inif, - v.IO_decl_val_egif, - v.IO_decl_val_ts, + dp.Asid(), + v.IO_decl_val_inif, + v.IO_decl_val_egif, + v.IO_decl_val_ts, v.IO_decl_val_beta)) } pred (dp DataPlaneSpec) dp4s_iospec_bio4s_decl(s IO_dp3s_state_local, t Place) { - // SIF: just copying with `TriggerBodyIoEnter` for now. - // NOTE: here as well we need some specific formatting + // NOTE: Formatting this differently leads to a parsing error. forall v IO_val :: { TriggerBodyIoDecl(v) } ( match v { case IO_decl_val{?beta, ?ts, ?inif, ?egif, ?sigma}: let _ignored := TriggerBodyIoDecl(v) in - // NOTE: Not sure I need this here - // (dp.Valid() && dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> (dp.dp4s_iospec_bio4s_decl_guard(s, t, v) ==> (CBio_IN_bio4s_decl(t, v) && dp.dp3s_iospec_ordered( - s, + s, dp4s_iospec_bio4s_decl_T(t, v)))) default: true @@ -369,14 +362,14 @@ requires token(t) && CBio_IN_bio3s_exit(t, v) ensures token(old(dp3s_iospec_bio3s_exit_T(t, v))) func Exit(ghost t Place, ghost v IO_val) -// SIF: declassification action -// TODO: maybe it's a good idea to split up sigma and p in IO_val ghost decreases requires token(t) && CBio_IN_bio4s_decl(t, v) -requires v.isIO_decl_val && low(v.IO_decl_val_inif) && low(v.IO_decl_val_egif) && low(v.IO_decl_val_ts) && low(v.IO_decl_val_beta) -ensures token(dp4s_iospec_bio4s_decl_T(t, v)) -ensures low(v.IO_decl_val_sigma) +requires v.isIO_decl_val && + low(v.IO_decl_val_inif) && low(v.IO_decl_val_egif) && + low(v.IO_decl_val_ts) && low(v.IO_decl_val_beta) +ensures token(dp4s_iospec_bio4s_decl_T(t, v)) +ensures low(v.IO_decl_val_sigma) func Decl(ghost t Place, ghost v IO_val) -/** End of helper functions to perfrom BIO operations **/ +/** End of helper functions to perform BIO operations **/ diff --git a/verification/io/values.gobra b/verification/io/values.gobra index 7c35c5281..5557c052c 100644 --- a/verification/io/values.gobra +++ b/verification/io/values.gobra @@ -53,11 +53,11 @@ ghost type IO_val adt { IO_Internal_val2_3 IO_ifs } - /* SIF: Output (to env.) for declassification action. - This consists of the segment identifier `beta`, the timestamp `ts, the - ingress and egress interfaces `inif` and `egif` (resp.), and a hop - authenticator `sigma`. - Also see `router.gobra` for types. */ + // Output (to env.) for declassification action (SIF). + // This consists of the segment identifier `beta`, the timestamp `ts`, the + // ingress and egress interfaces `inif` and `egif` (resp.), and a hop + // authenticator `sigma`. + // See `router.gobra`, in particular `hf_valid`, for why we need these values. IO_decl_val { IO_decl_val_beta set[IO_msgterm] // uinfo IO_decl_val_ts uint From e2e882784f211dbce0cd9cad4f10fe8f82720e14 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Mar 2025 10:20:39 +0100 Subject: [PATCH 034/104] Declassify concrete instead of abstract MAC tag I couldn't verify my changes to `io-spec-atomic-events.gobra` due to a magic wand in `dataplane.go`. Also, I couldn't import `pkg/slayers/path` in `io` as this leads to a cyclical import; thus, for the moment, I copied the relevant definitions in `io/tmp_defs.gobra` and assume that they equal the ones in `io_msgterm_spec.gobra`. --- router/io-spec-atomic-events.gobra | 19 +++++++++++-------- verification/io/io-spec.gobra | 5 +++-- verification/io/tmp_defs.gobra | 11 +++++++++++ verification/io/values.gobra | 16 +++++++++++----- 4 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 verification/io/tmp_defs.gobra diff --git a/router/io-spec-atomic-events.gobra b/router/io-spec-atomic-events.gobra index 952b49b31..5731e1f3f 100644 --- a/router/io-spec-atomic-events.gobra +++ b/router/io-spec-atomic-events.gobra @@ -27,6 +27,7 @@ import ( "sync" io "verification/io" gsync "verification/utils/ghost_sync" + "github.com/scionproto/scion/pkg/slayers/path" ) ghost @@ -161,25 +162,27 @@ func AtomicXover(oldPkt io.IO_pkt2, ingressID option[io.IO_ifs], newPkt io.IO_pk ghost requires low(beta) && low(ts) && low(inif) && low(egif) -requires sigma == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) +requires path.AbsMac(sigma) == io.nextMsgtermSpec(dp.Asid(), inif, egif, ts, beta) preserves acc(ioLock.LockP(), _) preserves ioLock.LockInv() == SharedInv!< dp, ioSharedArg !> -ensures low(sigma) +ensures forall i int :: { sigma[i] } 0 <= i && i < len(sigma) ==> low(sigma[i]) decreases _ -func AtomicDecl(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma io.IO_msgterm, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { +func AtomicDecl(beta set[io.IO_msgterm], ts uint, inif, egif option[io.IO_ifs], sigma [path.MacLen]byte, ioLock gpointer[gsync.GhostMutex], ioSharedArg SharedArg, dp io.DataPlaneSpec) { ghost ioLock.Lock() unfold SharedInv!< dp, ioSharedArg !>() t, s := *ioSharedArg.Place, *ioSharedArg.State - ghost tag := io.IO_val(io.IO_decl_val{beta, ts, inif, egif, sigma}) + ghost decl_val := io.IO_val(io.IO_decl_val{beta, ts, inif, egif, sigma}) - assert dp.dp4s_iospec_bio4s_decl_guard(s, t, tag) + // TODO: Remove this assumption + assume path.AbsMac(sigma) == io.AbsMac(sigma) + assert dp.dp4s_iospec_bio4s_decl_guard(s, t, decl_val) unfold dp.dp3s_iospec_ordered(s, t) unfold dp.dp4s_iospec_bio4s_decl(s, t) - io.TriggerBodyIoDecl(tag) - tN := io.dp4s_iospec_bio4s_decl_T(t, tag) - io.Decl(t, tag) + io.TriggerBodyIoDecl(decl_val) + tN := io.dp4s_iospec_bio4s_decl_T(t, decl_val) + io.Decl(t, decl_val) ghost *ioSharedArg.Place = tN fold SharedInv!< dp, ioSharedArg !>() diff --git a/verification/io/io-spec.gobra b/verification/io/io-spec.gobra index cca2684c7..f8107071e 100644 --- a/verification/io/io-spec.gobra +++ b/verification/io/io-spec.gobra @@ -315,7 +315,7 @@ decreases pure func (dp DataPlaneSpec) dp4s_iospec_bio4s_decl_guard(s IO_dp3s_state_local, t Place, v IO_val) bool { // Cf. `router.gobra`, in particular `hf_valid`. // NOTE: Formatting this differently leads to a parsing error. - return v.IO_decl_val_sigma == (nextMsgtermSpec( + return AbsMac(v.IO_decl_val_sigma) == (nextMsgtermSpec( dp.Asid(), v.IO_decl_val_inif, v.IO_decl_val_egif, @@ -369,7 +369,8 @@ requires v.isIO_decl_val && low(v.IO_decl_val_inif) && low(v.IO_decl_val_egif) && low(v.IO_decl_val_ts) && low(v.IO_decl_val_beta) ensures token(dp4s_iospec_bio4s_decl_T(t, v)) -ensures low(v.IO_decl_val_sigma) +ensures forall i int :: { v.IO_decl_val_sigma[i] } 0 <= i && i < len(v.IO_decl_val_sigma) ==> + low(v.IO_decl_val_sigma[i]) func Decl(ghost t Place, ghost v IO_val) /** End of helper functions to perform BIO operations **/ diff --git a/verification/io/tmp_defs.gobra b/verification/io/tmp_defs.gobra new file mode 100644 index 000000000..d80433b3d --- /dev/null +++ b/verification/io/tmp_defs.gobra @@ -0,0 +1,11 @@ +// +gobra + +package io + +// TODO: Remove this and link with actual definitions in "io_msgterm_spec.gobra" + +const MacLen = 6 + +ghost +decreases +pure func AbsMac(mac [MacLen]byte) IO_msgterm \ No newline at end of file diff --git a/verification/io/values.gobra b/verification/io/values.gobra index 5557c052c..4e7539b94 100644 --- a/verification/io/values.gobra +++ b/verification/io/values.gobra @@ -54,15 +54,21 @@ ghost type IO_val adt { } // Output (to env.) for declassification action (SIF). - // This consists of the segment identifier `beta`, the timestamp `ts`, the - // ingress and egress interfaces `inif` and `egif` (resp.), and a hop - // authenticator `sigma`. - // See `router.gobra`, in particular `hf_valid`, for why we need these values. + // The actual value to declassify is the hop authenticator (MAC tag) `sigma`; + // the other values act as the "tag" p to make the allowed declassifications + // deterministic in the low data (i.e., to make the IOD spec well-defined; + // see "Verifiable Security Policies for Distributed Systems" by Wolf et al.). + // `beta` is a segment identifier, `ts` a timestamp, and `inif` and `egif` + // the ingress and egress interfaces (resp.). + // See `hf_valid`, for why we need these values in particular. + // TODO: We might want to use the concrete instead of the abstract values for p. IO_decl_val { IO_decl_val_beta set[IO_msgterm] // uinfo IO_decl_val_ts uint IO_decl_val_inif option[IO_ifs] IO_decl_val_egif option[IO_ifs] - IO_decl_val_sigma IO_msgterm + // TODO: Relate to `path.MacLen` + IO_decl_val_sigma [MacLen]byte } + } \ No newline at end of file From c94f2298153f72c487c460a4a4865c7aecfe132e Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Mar 2025 10:58:30 +0100 Subject: [PATCH 035/104] Use `trusted` to suppress problems with closure implementation proof for now --- pkg/slayers/path/empty/empty.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/slayers/path/empty/empty.go b/pkg/slayers/path/empty/empty.go index 82bf48e00..1412af1b1 100644 --- a/pkg/slayers/path/empty/empty.go +++ b/pkg/slayers/path/empty/empty.go @@ -26,6 +26,8 @@ const PathLen = 0 const PathType path.Type = 0 +// TODO: Once Gobra issue 878 is resolved, remove `truested`. +// @ trusted // @ requires path.PathPackageMem() // @ requires !path.Registered(PathType) // @ ensures path.PathPackageMem() From d16b7c9dd3cb46fff633d06045766fb276cb4d1a Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Mar 2025 13:58:49 +0100 Subject: [PATCH 036/104] Verify pkg/slayers/path At the moment, I'm using pure functions to access fields due to Gobra issue 883. Maybe this should be changed once that bug is resolved. --- pkg/slayers/path/hopfield.go | 8 +++++--- pkg/slayers/path/hopfield_spec.gobra | 15 +++++++++++++++ pkg/slayers/path/infofield.go | 4 +++- pkg/slayers/path/mac.go | 2 ++ pkg/slayers/path/path.go | 5 ++++- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 9b08a9d69..0dac98421 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -79,7 +79,7 @@ type HopField struct { // @ preserves acc(sl.Bytes(raw, 0, HopLen), R45) // @ ensures h.Mem() // @ ensures err == nil -// @ ensures BytesToIO_HF(raw, 0, 0, HopLen) == +// @ ensures BytesToIO_HF(raw, 0, 0, HopLen) == // @ unfolding acc(h.Mem(), R10) in h.ToIO_HF() // @ decreases func (h *HopField) DecodeFromBytes(raw []byte) (err error) { @@ -110,10 +110,12 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { // SerializeTo writes the fields into the provided buffer. The buffer must be of length >= // path.HopLen. // @ requires len(b) >= HopLen -// @ preserves acc(h.Mem(), R10) +// @ requires acc(h.Mem(), R10) +// @ requires low(h.GetEgressRouterAlert()) && low(h.GetIngressRouterAlert()) // @ preserves sl.Bytes(b, 0, HopLen) +// @ ensures acc(h.Mem(), R10) // @ ensures err == nil -// @ ensures BytesToIO_HF(b, 0, 0, HopLen) == +// @ ensures BytesToIO_HF(b, 0, 0, HopLen) == // @ unfolding acc(h.Mem(), R10) in h.ToIO_HF() // @ decreases func (h *HopField) SerializeTo(b []byte) (err error) { diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index d04abd94d..3c1795bbe 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -115,3 +115,18 @@ pure func (h HopField) ToIO_HF() (io.IO_HF) { HVF: AbsMac(h.Mac), } } +// TODO: Once Gobra issue 883 is removed, these could be removed in favor of +// unfolding permissions directly in the contract. +ghost +requires h.Mem() +decreases +pure func (h *HopField) GetEgressRouterAlert() bool { + return unfolding h.Mem() in h.EgressRouterAlert +} + +ghost +requires h.Mem() +decreases +pure func (h *HopField) GetIngressRouterAlert() bool { + return unfolding h.Mem() in h.IngressRouterAlert +} \ No newline at end of file diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index f3488e768..5bf0973c3 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -88,8 +88,10 @@ func (inf *InfoField) DecodeFromBytes(raw []byte) (err error) { // SerializeTo writes the fields into the provided buffer. The buffer must be of length >= // path.InfoLen. // @ requires len(b) >= InfoLen -// @ preserves acc(inf, R10) +// @ requires acc(inf, R10) +// @ requires low(inf.ConsDir) && low(inf.Peer) // @ preserves slices.Bytes(b, 0, len(b)) +// @ ensures acc(inf, R10) // @ ensures err == nil // @ ensures inf.ToAbsInfoField() == // @ BytesToAbsInfoField(b, 0) diff --git a/pkg/slayers/path/mac.go b/pkg/slayers/path/mac.go index df11254b7..b48286598 100644 --- a/pkg/slayers/path/mac.go +++ b/pkg/slayers/path/mac.go @@ -30,6 +30,7 @@ const MACBufferSize = 16 // this method does not modify info or hf. // Modifying the provided buffer after calling this function may change the returned HopField MAC. // @ requires h != nil && h.Mem() +// @ requires low(len(buffer) < MACBufferSize) // @ preserves len(buffer) >= MACBufferSize ==> sl.Bytes(buffer, 0, len(buffer)) // @ ensures h.Mem() // @ decreases @@ -47,6 +48,7 @@ func MAC(h hash.Hash, info InfoField, hf HopField, buffer []byte) [MacLen]byte { // Modifying the provided buffer after calling this function may change the returned HopField MAC. // In contrast to MAC(), FullMAC returns all the 16 bytes instead of only 6 bytes of the MAC. // @ requires h != nil && h.Mem() +// @ requires low(len(buffer) < MACBufferSize) // @ preserves len(buffer) >= MACBufferSize ==> sl.Bytes(buffer, 0, len(buffer)) // @ ensures h.Mem() // @ ensures len(res) == MACBufferSize && sl.Bytes(res, 0, MACBufferSize) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 76d175d87..448152b3f 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -48,7 +48,9 @@ func init() { type Type uint8 // @ requires 0 <= t && t < maxPathType -// @ preserves acc(PathPackageMem(), R20) +// @ requires acc(PathPackageMem(), R20) +// @ requires low(Registered(t)) +// @ ensures acc(PathPackageMem(), R20) // @ decreases func (t Type) String() string { //@ unfold acc(PathPackageMem(), R20) @@ -183,6 +185,7 @@ func StrictDecoding(strict bool) { // NewPath returns a new path object of pathType. // @ requires 0 <= pathType && pathType < maxPathType // @ requires acc(PathPackageMem(), _) +// @ requires low(Registered(pathType)) && low(IsStrictDecoding()) // @ ensures e != nil ==> e.ErrorMem() // @ ensures e == nil ==> p != nil && p.NonInitMem() // @ decreases From f0ced95b3b66a3e358979353768c7e1c3a088b89 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Mar 2025 15:37:12 +0100 Subject: [PATCH 037/104] Verify pkg/slayers/path/onehop --- pkg/slayers/path/onehop/onehop.go | 22 ++++++++++++++++------ pkg/slayers/path/onehop/onehop_spec.gobra | 21 ++++++++++++++++++++- pkg/slayers/path/path.go | 9 ++++++++- pkg/slayers/path/path_spec.gobra | 2 ++ pkg/slayers/path/scion/decoded_spec.gobra | 3 +++ pkg/slayers/path/scion/raw_spec.gobra | 2 ++ 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pkg/slayers/path/onehop/onehop.go b/pkg/slayers/path/onehop/onehop.go index 868147f76..928f95dda 100644 --- a/pkg/slayers/path/onehop/onehop.go +++ b/pkg/slayers/path/onehop/onehop.go @@ -29,6 +29,8 @@ const PathLen = path.InfoLen + 2*path.HopLen const PathType path.Type = 2 +// TODO: Once Gobra issue 878 is resolved, remove `truested`. +// @ trusted // @ requires path.PathPackageMem() // @ requires !path.Registered(PathType) // @ ensures path.PathPackageMem() @@ -66,6 +68,7 @@ type Path struct { } // @ requires o.NonInitMem() +// @ requires low(len(data) < PathLen) // @ preserves acc(sl.Bytes(data, 0, len(data)), R42) // @ ensures (len(data) >= PathLen) == (r == nil) // @ ensures r == nil ==> o.Mem(data) @@ -100,6 +103,7 @@ func (o *Path) DecodeFromBytes(data []byte) (r error) { return r } +// @ requires low(len(b) < PathLen) // @ preserves acc(o.Mem(ubuf), R1) // @ preserves acc(sl.Bytes(ubuf, 0, len(ubuf)), R1) // @ preserves sl.Bytes(b, 0, len(b)) @@ -137,10 +141,13 @@ func (o *Path) SerializeTo(b []byte /*@, ubuf []byte @*/) (err error) { // ToSCIONDecoded converts the one hop path in to a normal SCION path in the // decoded format. -// @ preserves o.Mem(ubuf) +// @ requires o.Mem(ubuf) +// @ requires low(o.GetSecondHopConsIngress(ubuf) == 0) // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) +// @ ensures o.Mem(ubuf) // @ ensures err == nil ==> (sd != nil && sd.Mem(ubuf)) // @ ensures err != nil ==> err.ErrorMem() +// @ ensures low(err != nil) // @ decreases func (o *Path) ToSCIONDecoded( /*@ ghost ubuf []byte @*/ ) (sd *scion.Decoded, err error) { //@ unfold acc(o.Mem(ubuf), R1) @@ -200,14 +207,17 @@ func (o *Path) ToSCIONDecoded( /*@ ghost ubuf []byte @*/ ) (sd *scion.Decoded, e } // Reverse a OneHop path that returns a reversed SCION path. -// @ requires o.Mem(ubuf) +// @ requires o.Mem(ubuf) +// requires low(o.GetSecondHopConsIngress(ubuf) == 0) +// @ requires o.LowReverse() // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) -// @ ensures err == nil ==> p != nil -// @ ensures err == nil ==> p.Mem(ubuf) -// @ ensures err == nil ==> typeOf(p) == type[*scion.Decoded] -// @ ensures err != nil ==> err.ErrorMem() +// @ ensures err == nil ==> p != nil +// @ ensures err == nil ==> p.Mem(ubuf) +// @ ensures err == nil ==> typeOf(p) == type[*scion.Decoded] +// @ ensures err != nil ==> err.ErrorMem() // @ decreases func (o *Path) Reverse( /*@ ghost ubuf []byte @*/ ) (p path.Path, err error) { + // @ o.GetLowReverse(ubuf, 1/2) sp, err := o.ToSCIONDecoded( /*@ ubuf @*/ ) if err != nil { return nil, serrors.WrapStr("converting to scion path", err) diff --git a/pkg/slayers/path/onehop/onehop_spec.gobra b/pkg/slayers/path/onehop/onehop_spec.gobra index f81b9b982..5832ea7b0 100644 --- a/pkg/slayers/path/onehop/onehop_spec.gobra +++ b/pkg/slayers/path/onehop/onehop_spec.gobra @@ -64,4 +64,23 @@ pure func (p *Path) LenSpec(ghost ub []byte) int { return PathLen } -(*Path) implements path.Path \ No newline at end of file +(*Path) implements path.Path + +// TODO: Once Gobra issue 883 is removed, this could be removed in favor of +// unfolding permissions directly in the contract. +ghost +requires o.Mem(ub) +decreases +pure func (o *Path) GetSecondHopConsIngress(ghost ub []byte) uint16 { + return unfolding o.Mem(ub) in + unfolding o.SecondHop.Mem() in o.SecondHop.ConsIngress +} + +pred (o *Path) LowReverse() + +ghost +requires o.LowReverse() && p > 0 +preserves acc(o.Mem(ub), p) +ensures low(o.GetSecondHopConsIngress(ub) == 0) +decreases +func (o *Path) GetLowReverse(ghost ub []byte, ghost p perm) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 76d175d87..404316336 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -70,6 +70,7 @@ type Path interface { // SerializeTo serializes the path into the provided buffer. // (VerifiedSCION) There are implementations of this interface that modify the underlying // structure when serializing (e.g. scion.Raw) + //@ requires low(len(b)) //@ preserves sl.Bytes(ub, 0, len(ub)) //@ preserves acc(Mem(ub), R1) //@ preserves sl.Bytes(b, 0, len(b)) @@ -80,7 +81,7 @@ type Path interface { // (VerifiedSCION) There are implementations of this interface (e.g., scion.Raw) that // store b and use it as internal data. //@ requires NonInitMem() - //@ requires low(len(b)) + //@ requires low(len(b)) //@ preserves acc(sl.Bytes(b, 0, len(b)), R42) //@ ensures err == nil ==> Mem(b) //@ ensures err == nil ==> IsValidResultOfDecoding(b) @@ -88,6 +89,11 @@ type Path interface { //@ ensures err != nil ==> NonInitMem() //@ decreases DecodeFromBytes(b []byte) (err error) + // TODO: Once Gobra issue 846 is resolved, rework this. + // Predicate encapsulating sensitivity. Intended to be implemented as an + // abstract predicate, in conjunction with an abstract function requiring + // LowReverse() and ensuring the corresponding sensitivity. + //@ pred LowReverse() //@ ghost //@ pure //@ requires Mem(b) @@ -97,6 +103,7 @@ type Path interface { // Reverse reverses a path such that it can be used in the reversed direction. // XXX(shitz): This method should possibly be moved to a higher-level path manipulation package. //@ requires Mem(ub) + //@ requires LowReverse() //@ preserves sl.Bytes(ub, 0, len(ub)) //@ ensures e == nil ==> p != nil //@ ensures e == nil ==> p.Mem(ub) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index 57711e1d0..82be98c96 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -52,6 +52,8 @@ pure func (p *rawPath) LenSpec(ghost ub []byte) (l int) { len(p.raw) } +pred (r *rawPath) LowReverse() + (*rawPath) implements Path /** End of rawPath spec **/ diff --git a/pkg/slayers/path/scion/decoded_spec.gobra b/pkg/slayers/path/scion/decoded_spec.gobra index 1ebbce5a3..bc14af13a 100644 --- a/pkg/slayers/path/scion/decoded_spec.gobra +++ b/pkg/slayers/path/scion/decoded_spec.gobra @@ -47,6 +47,8 @@ pred (d *Decoded) Mem(ubuf []byte) { (forall i int :: { &d.HopFields[i] } 0 <= i && i < len(d.HopFields) ==> d.HopFields[i].Mem()) } +pred (d *Decoded) LowReverse() + /**** End of Predicates ****/ /**** Stubs ****/ @@ -112,6 +114,7 @@ ensures e == nil ==> ( d.LenSpec(ubuf) == old(d.LenSpec(ubuf)) && (old(d.GetBase(ubuf).Valid()) ==> d.GetBase(ubuf).Valid())) ensures e != nil ==> d.NonInitMem() && e.ErrorMem() +ensures low(e != nil) decreases func (d *Decoded) IncPath(ghost ubuf []byte) (e error) { unfold d.Mem(ubuf) diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index 33b4c0add..5f705dbd3 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -37,6 +37,8 @@ pred (s *Raw) Mem(buf []byte) { s.Raw === buf[:len(s.Raw)] && len(s.Raw) == s.Base.Len() } + +pred (s *Raw) LowReverse() /**** End of Predicates ****/ (*Raw) implements path.Path From f63a7d6da7a4a6050c4f6a97dd2ff7a28e1de74f Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 11 Mar 2025 15:48:10 +0100 Subject: [PATCH 038/104] Verify pkg/slayers/path/epic --- pkg/slayers/path/epic/epic.go | 7 +++++++ pkg/slayers/path/epic/epic_spec.gobra | 11 ++++++++++- pkg/slayers/path/path.go | 7 +++++++ pkg/slayers/path/path_spec.gobra | 2 ++ pkg/slayers/path/scion/decoded_spec.gobra | 2 ++ pkg/slayers/path/scion/raw.go | 2 ++ pkg/slayers/path/scion/raw_spec.gobra | 3 +++ 7 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pkg/slayers/path/epic/epic.go b/pkg/slayers/path/epic/epic.go index c9d770163..8fe25c99e 100644 --- a/pkg/slayers/path/epic/epic.go +++ b/pkg/slayers/path/epic/epic.go @@ -41,6 +41,8 @@ const ( ) // RegisterPath registers the EPIC path type globally. +// TODO: Once Gobra issue 878 is resolved, remove `truested`. +// @ trusted // @ requires path.PathPackageMem() // @ requires !path.Registered(PathType) // @ ensures path.PathPackageMem() @@ -80,6 +82,8 @@ type Path struct { // SerializeTo serializes the Path into buffer b. On failure, an error is returned, otherwise // SerializeTo will return nil. +// @ requires p.LowSerializeTo() +// @ requires low(len(b)) // @ preserves acc(p.Mem(ubuf), R1) // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) // @ preserves sl.Bytes(b, 0, len(b)) @@ -90,6 +94,7 @@ type Path struct { // @ ensures old(p.getLHVFLen(ubuf)) != HVFLen ==> r != nil // @ decreases func (p *Path) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { + // @ p.GetLowSerializeTo(ubuf, R1/2) if len(b) < p.Len( /*@ ubuf @*/ ) { return serrors.New("buffer too small to serialize path.", "expected", int(p.Len( /*@ ubuf @*/ )), "actual", len(b)) @@ -138,6 +143,7 @@ func (p *Path) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { // DecodeFromBytes deserializes the buffer b into the Path. On failure, an error is returned, // otherwise SerializeTo will return nil. // @ requires p.NonInitMem() +// @ requires low(len(b) < MetadataLen) // @ preserves acc(sl.Bytes(b, 0, len(b)), R42) // @ ensures len(b) < MetadataLen ==> r != nil // @ ensures r == nil ==> p.Mem(b) @@ -213,6 +219,7 @@ func (p *Path) Reverse( /*@ ghost ubuf []byte @*/ ) (ret path.Path, r error) { // Len returns the length of the EPIC path in bytes. // @ preserves acc(p.Mem(ubuf), R50) // @ ensures l == p.LenSpec(ubuf) +// @ ensures low(l) // @ decreases func (p *Path) Len( /*@ ghost ubuf []byte @*/ ) (l int) { // @ unfold acc(p.Mem(ubuf), R50) diff --git a/pkg/slayers/path/epic/epic_spec.gobra b/pkg/slayers/path/epic/epic_spec.gobra index 950ceb4b1..5082f9a82 100644 --- a/pkg/slayers/path/epic/epic_spec.gobra +++ b/pkg/slayers/path/epic/epic_spec.gobra @@ -102,4 +102,13 @@ pure func (p *Path) IsValidResultOfDecoding(b []byte) (res bool) { return true } -(*Path) implements path.Path \ No newline at end of file +(*Path) implements path.Path + +pred (*Path) LowSerializeTo() + +ghost +requires ep.LowSerializeTo() && p > 0 +preserves acc(ep.Mem(buf), p) +ensures low(ep.getPHVFLen(buf) != HVFLen) && low(ep.getLHVFLen(buf) != HVFLen) +decreases +func (ep *Path) GetLowSerializeTo(buf []byte, ghost p perm) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 448152b3f..ba197b56f 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -72,6 +72,13 @@ type Path interface { // SerializeTo serializes the path into the provided buffer. // (VerifiedSCION) There are implementations of this interface that modify the underlying // structure when serializing (e.g. scion.Raw) + // TODO: Once Gobra issue 846 is resolved, rework this. + // Predicate containing sensitivity information. Intended to be implemented + // as an abstract predicate, alongside an abstract function requiring + // `LowSerializeTo()`, and ensuring the corresponding sensitivity. + //@ pred LowSerializeTo() + //@ requires LowSerializeTo() + //@ requires low(len(b)) //@ preserves sl.Bytes(ub, 0, len(ub)) //@ preserves acc(Mem(ub), R1) //@ preserves sl.Bytes(b, 0, len(b)) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index 57711e1d0..839471ea8 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -28,6 +28,8 @@ pred (r *rawPath) NonInitMem() { acc(r) } +pred (*rawPath) LowSerializeTo() + ghost requires p.Mem(buf) ensures p.NonInitMem() diff --git a/pkg/slayers/path/scion/decoded_spec.gobra b/pkg/slayers/path/scion/decoded_spec.gobra index 1ebbce5a3..6329c672a 100644 --- a/pkg/slayers/path/scion/decoded_spec.gobra +++ b/pkg/slayers/path/scion/decoded_spec.gobra @@ -47,6 +47,8 @@ pred (d *Decoded) Mem(ubuf []byte) { (forall i int :: { &d.HopFields[i] } 0 <= i && i < len(d.HopFields) ==> d.HopFields[i].Mem()) } +pred (*Decoded) LowSerializeTo() + /**** End of Predicates ****/ /**** Stubs ****/ diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index 4d38b812b..da51439ca 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -41,6 +41,7 @@ type Raw struct { // @ s.GetBase(data).WeaklyValid() && // @ s.GetBase(data).EqAbsHeader(data) // @ ensures res != nil ==> (s.NonInitMem() && res.ErrorMem()) +// @ ensures low(res == nil) // @ decreases func (s *Raw) DecodeFromBytes(data []byte) (res error) { //@ unfold s.NonInitMem() @@ -105,6 +106,7 @@ func (s *Raw) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { // @ ensures err == nil ==> p != nil && p != (*Raw)(nil) // @ ensures err == nil ==> p.Mem(ubuf) // @ ensures err != nil ==> err.ErrorMem() +// @ ensures low(err != nil) // @ decreases func (s *Raw) Reverse( /*@ ghost ubuf []byte @*/ ) (p path.Path, err error) { // XXX(shitz): The current implementation is not the most performant, since it parses the entire diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index 33b4c0add..4af4a9d5c 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -37,6 +37,8 @@ pred (s *Raw) Mem(buf []byte) { s.Raw === buf[:len(s.Raw)] && len(s.Raw) == s.Base.Len() } + +pred (s *Raw) LowSerializeTo() /**** End of Predicates ****/ (*Raw) implements path.Path @@ -73,6 +75,7 @@ pure func (s *Raw) Type(ghost buf []byte) path.Type { */ preserves acc(s.Mem(buf), R50) ensures l == s.LenSpec(buf) +ensures low(l) decreases func (s *Raw) Len(ghost buf []byte) (l int) { return unfolding acc(s.Mem(buf), _) in s.Base.Len() From 01950a7d4661aa4243286837c01a511f8638b89e Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 13 Mar 2025 11:32:12 +0100 Subject: [PATCH 039/104] Fix typo --- pkg/slayers/scion_spec.gobra | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/slayers/scion_spec.gobra b/pkg/slayers/scion_spec.gobra index 1b45a2896..e45ae6064 100644 --- a/pkg/slayers/scion_spec.gobra +++ b/pkg/slayers/scion_spec.gobra @@ -441,7 +441,7 @@ pure func (s *SCION) ValidScionInitSpec(ub []byte) bool { let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := s.HdrLen*LineLen in typeOf(s.Path) == (*scion.Raw) && - s.Path.(*scion.Raw).GetBase(ub[low__:high]).WeaklyValid() + s.Path.(*scion.Raw).GetBase(ub[low_:high]).WeaklyValid() } // Checks if the common path header is valid in the serialized scion packet. From 6c3067e17c8c01ac149ab01812235621a153e374 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 10 Apr 2025 13:13:51 +0200 Subject: [PATCH 040/104] Clean up - Still contains workaround for bug 835/890 - Still contains workaround for missing feature 846 --- pkg/slayers/scion_spec.gobra | 2 +- private/topology/underlay/defs.go | 9 ++++++--- verification/dependencies/fmt/fmt.gobra | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/slayers/scion_spec.gobra b/pkg/slayers/scion_spec.gobra index 1b45a2896..e45ae6064 100644 --- a/pkg/slayers/scion_spec.gobra +++ b/pkg/slayers/scion_spec.gobra @@ -441,7 +441,7 @@ pure func (s *SCION) ValidScionInitSpec(ub []byte) bool { let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := s.HdrLen*LineLen in typeOf(s.Path) == (*scion.Raw) && - s.Path.(*scion.Raw).GetBase(ub[low__:high]).WeaklyValid() + s.Path.(*scion.Raw).GetBase(ub[low_:high]).WeaklyValid() } // Checks if the common path header is valid in the serialized scion packet. diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 1dc48eed9..61e833a7a 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -73,9 +73,12 @@ func TypeFromString(s string) (t Type, err error) { case strings.ToLower(UDPIPv46Name): return UDPIPv46, nil default: - // TODO: Once Gobra issue #835 is resolved, remove this assumption. - //@ ghost errCtx := []interface{}{"type", s} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. + // @ ghost errCtx := []interface{}{"type", s} + // @ assert len(errCtx) == 2 + // @ assert low(errCtx[0]) + // @ assert low(errCtx[1]) + // @ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return Invalid, serrors.New("Unknown underlay type", "type", s) } } diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 417568062..84d0cb14b 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -24,8 +24,8 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `v` // using a pure Boolean function. -ensures (low(format) && forall i int :: { &v[i] } 0 <= i && i < len(v) ==> - low(v[i])) ==> low(res) +ensures (low(format) && low(len(v)) && + forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From 439ff5481931ae3969e3de4e314cdd2f39f5231b Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 12:20:52 +0200 Subject: [PATCH 041/104] Fix contract of `serrors.New` --- pkg/private/serrors/serrors_spec.gobra | 13 +++++++------ private/topology/underlay/defs.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 6ab193164..ff7587358 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -90,13 +90,14 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) -ensures res != nil && res.ErrorMem() -ensures res.IsDuplicableMem() +requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` // using a pure Boolean function. -ensures (low(msg) && low(len(errCtx)) && - forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> - low(errCtx[i])) ==> low(res) +requires low(msg) && low(len(errCtx)) && + forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) +ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +ensures res != nil && res.ErrorMem() +ensures res.IsDuplicableMem() +ensures low(res != nil) decreases func New(msg string, errCtx ...interface{}) (res error) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 61e833a7a..f7badb21d 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -63,7 +63,7 @@ func (o Type) String() (res string) { } // @ requires low(s) -// @ ensures low(t) && low(err) +// @ ensures low(t) && low(err != nil) func TypeFromString(s string) (t Type, err error) { switch strings.ToLower(s) { case strings.ToLower(UDPIPv4Name): From 1e0500bd816959aa82570f4711e6eb48566f1999 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 13:35:48 +0200 Subject: [PATCH 042/104] Clean up --- private/topology/linktype.go | 55 ++++++++++++------------ verification/utils/sif/assumptions.gobra | 2 +- verification/utils/sif/definitions.gobra | 17 -------- 3 files changed, 28 insertions(+), 46 deletions(-) delete mode 100644 verification/utils/sif/definitions.gobra diff --git a/private/topology/linktype.go b/private/topology/linktype.go index b86e21140..a276265e9 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -46,10 +46,8 @@ const ( ) // @ requires low(l) -// SIF: If I wanted to assert `low(res)`, I would need to annotate `error.Error` -// in `builtin.gobra`. // @ decreases -func (l LinkType) String() (res string) { +func (l LinkType) String() string { if l == Unset { return "unset" } @@ -57,8 +55,7 @@ func (l LinkType) String() (res string) { if err != nil { return err.Error() } - //@ unfold sif.LowBytes(s, 0, len(s)) - //@ sif.AssumeLowSliceToLowString(s, 1/1) + //@ unfold sl.Bytes(s, 0, len(s)) return string(s) } @@ -70,11 +67,9 @@ func (l LinkType) String() (res string) { func LinkTypeFromString(s string) (res LinkType) { var l /*@@@*/ LinkType tmp := []byte(s) - // SIF: For now I have to make this assumption, see Gobra issue #831 - // Note that we can already infer low(len(tmp)), as the lengths are related - // in the Viper encoding, but not the contents. + // TODO: Once Gobra issue #831 is resolved, remove this assumption. //@ assume forall i int :: { &tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sif.LowBytes(tmp, 0, len(tmp)) + //@ fold sl.Bytes(tmp, 0, len(tmp)) if err := l.UnmarshalText(tmp); err != nil { return Unset } @@ -83,56 +78,60 @@ func LinkTypeFromString(s string) (res LinkType) { // @ requires low(l) // @ ensures (l == Core || l == Parent || l == Child || l == Peer) == (err == nil) -// @ ensures err == nil ==> sif.LowBytes(res, 0, len(res)) +// @ ensures err == nil ==> sl.Bytes(res, 0, len(res)) +// @ ensures err == nil ==> low(len(res)) && +// @ forall i int :: { sl.GetByte(res, 0, len(res), i) } 0 <= i && i < len(res) ==> +// @ low(sl.GetByte(res, 0, len(res), i)) // @ ensures err != nil ==> err.ErrorMem() -// SIF: To make the postconditions as precise as possible, I have not put this -// assertion behind `err == nil` or `err != nil` (resp.) -// Only for LowBytes(...) I have, as that only makes sense when `res != nil`, -// and I have added `low(res)` for `err != nil` appropriately. -// @ ensures low(len(res)) && low(err) -// @ ensures err != nil ==> low(res) +// @ ensures low(err != nil) // @ decreases func (l LinkType) MarshalText() (res []byte, err error) { switch l { case Core: tmp := []byte("core") - // SIF: ATM we need this assumption, see Gobra issue #831 + // TODO: Once Gobra issue #831 is resolved, remove this assumption. //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sif.LowBytes(tmp, 0, len(tmp)) + //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Parent: tmp := []byte("parent") + // TODO: Once Gobra issue #831 is resolved, remove this assumption. //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sif.LowBytes(tmp, 0, len(tmp)) + //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Child: tmp := []byte("child") + // TODO: Once Gobra issue #831 is resolved, remove this assumption. //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sif.LowBytes(tmp, 0, len(tmp)) + //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Peer: tmp := []byte("peer") + // TODO: Once Gobra issue #831 is resolved, remove this assumption. //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) - //@ fold sif.LowBytes(tmp, 0, len(tmp)) + //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil default: return nil, serrors.New("invalid link type") } } -// @ requires low(len(data)) -// @ requires acc(sif.LowBytes(data, 0, len(data)), R15) +// @ requires acc(sl.Bytes(data, 0, len(data)), R15) +// @ requires low(len(data)) && +// @ forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) ==> +// @ low(sl.GetByte(data, 0, len(data), i)) // @ preserves acc(l) // @ ensures acc(sl.Bytes(data, 0, len(data)), R15) // @ ensures err != nil ==> err.ErrorMem() -// SIF: As *l remains unchanged if err != nil, and we don't know if low(*l) before // @ ensures err == nil ==> low(*l) -// SIF: We need `low(err)` regardless of `err ?= nil` as we use it in branch conditions. -// @ ensures low(err) +// @ ensures low(err != nil) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { - //@ unfold acc(sif.LowBytes(data, 0, len(data)), R15) + //@ BeforeUnfold: + //@ unfold acc(sl.Bytes(data, 0, len(data)), R15) //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R15) + //@ assert forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) ==> + //@ old[BeforeUnfold](sl.GetByte(data, 0, len(data), i)) == data[i] //@ sif.AssumeLowSliceToLowString(data, R15) switch strings.ToLower(string(data)) { case "core": @@ -144,7 +143,7 @@ func (l *LinkType) UnmarshalText(data []byte) (err error) { case "peer": *l = Peer default: - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ ghost errCtx := []interface{}{"linkType", string(data)} //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return serrors.New("invalid link type", "linkType", string(data)) diff --git a/verification/utils/sif/assumptions.gobra b/verification/utils/sif/assumptions.gobra index daa742bd8..33f20918d 100644 --- a/verification/utils/sif/assumptions.gobra +++ b/verification/utils/sif/assumptions.gobra @@ -2,7 +2,7 @@ package sif -// SIF: See Gobra issue #832 +// TODO: Once Gobra issue #832 is resolved, introduce body and prove this. ghost requires p > 0 && acc(b, p) requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) ==> low(b[i]) diff --git a/verification/utils/sif/definitions.gobra b/verification/utils/sif/definitions.gobra deleted file mode 100644 index 8a045118b..000000000 --- a/verification/utils/sif/definitions.gobra +++ /dev/null @@ -1,17 +0,0 @@ -// +gobra - -package sif - -// SIF: `slices.Bytes` with the addition of asserting the elements to be low. -// TODO: I might want to eventually decouple this from access permissions -// and put sensitivity in a pure Boolean function -pred LowBytes(s []byte, start int, end int) { - // start inclusive - 0 <= start && - start <= end && - // end exclusive - end <= cap(s) && - // SIF: Might be useful in the future - // low(end - start) && - forall i int :: { &s[i] } start <= i && i < end ==> acc(&s[i]) && low(s[i]) -} \ No newline at end of file From 29e7444b2908f72810d22ada938b93fbec598d7c Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 13:44:41 +0200 Subject: [PATCH 043/104] Rename `AssumeLowSliceToLowString` to `LowSliceImpliesLowString` --- private/topology/linktype.go | 2 +- verification/utils/sif/assumptions.gobra | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index a276265e9..8c32a1a1e 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -132,7 +132,7 @@ func (l *LinkType) UnmarshalText(data []byte) (err error) { //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R15) //@ assert forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) ==> //@ old[BeforeUnfold](sl.GetByte(data, 0, len(data), i)) == data[i] - //@ sif.AssumeLowSliceToLowString(data, R15) + //@ sif.LowSliceImpliesLowString(data, R15) switch strings.ToLower(string(data)) { case "core": *l = Core diff --git a/verification/utils/sif/assumptions.gobra b/verification/utils/sif/assumptions.gobra index 33f20918d..8a612bee3 100644 --- a/verification/utils/sif/assumptions.gobra +++ b/verification/utils/sif/assumptions.gobra @@ -8,4 +8,4 @@ requires p > 0 && acc(b, p) requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) ==> low(b[i]) ensures acc(b, p) && low(string(b)) decreases -func AssumeLowSliceToLowString(b []byte, p perm) +func LowSliceImpliesLowString(b []byte, p perm) From 890558a52a148421a62f2b84bff048f2de3e881f Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 14:12:29 +0200 Subject: [PATCH 044/104] Stop using `sif.LowBytes` --- pkg/addr/isdas.go | 24 +++++++++++++----------- pkg/addr/isdas_spec.gobra | 6 ++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 15f807860..1d4f7951c 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -76,7 +76,7 @@ type AS uint64 // space) or ipv6-style hex (in the case of SCION-only AS numbers) string. // @ requires low(_as) // @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr) +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func ParseAS(_as string) (retAs AS, retErr error) { return parseAS(_as, ":") @@ -84,7 +84,7 @@ func ParseAS(_as string) (retAs AS, retErr error) { // @ requires low(_as) && low(sep) // @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr) +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func parseAS(_as string, sep string) (retAs AS, retErr error) { parts := strings.Split(_as, sep) @@ -185,14 +185,15 @@ func (_as AS) MarshalText() ([]byte, error) { return []byte(_as.String()), nil } -// @ requires sif.LowBytes(text, 0, len(text)) +// @ requires forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) +// @ requires low(len(text)) && +// @ forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) // @ preserves acc(_as) -// @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) +// @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases func (_as *AS) UnmarshalText(text []byte) error { - //@ unfold sif.LowBytes(text, 0, len(text)) // SIF: See Gobra issue #832 - //@ assume low(string(text)) + //@ sif.LowSliceImpliesLowString(text, 1/1) parsed, err := ParseAS(string(text)) if err != nil { return err @@ -239,7 +240,7 @@ func IAFrom(isd ISD, _as AS) (ia IA, err error) { // ParseIA parses an IA from a string of the format 'isd-as'. // @ requires low(ia) -// @ ensures low(retErr) +// @ ensures low(retErr != nil) // @ decreases func ParseIA(ia string) (retIA IA, retErr error) { parts := strings.Split(ia, "-") @@ -280,14 +281,15 @@ func (ia IA) MarshalText() ([]byte, error) { return []byte(ia.String()), nil } -// @ requires low(len(b)) && sif.LowBytes(b, 0, len(b)) +// @ requires forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) +// @ requires low(len(b)) && +// @ forall i int :: { b[i] } 0 <= i && i < len(b) ==> low(b[i]) // @ preserves acc(ia) -// @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) +// @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases func (ia *IA) UnmarshalText(b []byte) error { - //@ unfold sif.LowBytes(b, 0, len(b)) // SIF: See Gobra issue #832 - //@ assume low(string(b)) + //@ sif.LowSliceImpliesLowString(b, 1/1) parsed, err := ParseIA(string(b)) if err != nil { return err diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 7458a9ccf..11e03a815 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -30,7 +30,8 @@ pred (ia *IA) LowMem() { acc(ia) && low(*ia) } pred (ia *IA) LowSet(s string) { low(s) } pred (*IA) LowUnmarshalText(text []byte) { - low(len(text)) && sif.LowBytes(text, 0, len(text)) + low(len(text)) && + forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) && low(text[i]) } (*IA) implements encoding.TextUnmarshaler { @@ -60,7 +61,8 @@ IA implements fmt.Stringer { pred (_as *AS) Mem() { acc(_as) } pred (*AS) LowUnmarshalText(text []byte) { - low(len(text)) && sif.LowBytes(text, 0, len(text)) + low(len(text)) && + forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) && low(text[i]) } (*AS) implements encoding.TextUnmarshaler { From a83dde0878b54b1c08d876a7f23f185d04cabcf2 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 16:36:27 +0200 Subject: [PATCH 045/104] Clean up --- pkg/addr/fmt.go | 14 +++----- pkg/addr/host.go | 13 +++----- pkg/addr/host_spec.gobra | 5 --- pkg/addr/isdas.go | 33 ++++++++----------- pkg/addr/isdas_spec.gobra | 15 --------- pkg/private/serrors/serrors_spec.gobra | 12 ++++--- .../dependencies/encoding/encoding.gobra | 13 ++++---- verification/dependencies/flag/flag.gobra | 11 +++---- verification/dependencies/fmt/fmt.gobra | 2 +- verification/dependencies/net/ip.gobra | 6 ---- .../dependencies/strings/builder.gobra | 1 + 11 files changed, 43 insertions(+), 82 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index df465f759..4dedd0506 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -112,14 +112,12 @@ func FormatAS(as_ AS, opts ...FormatOption) string { } // @ requires as_.inRange() -// @ requires low(as_) -// SIF: Remove eventually maybe -// @ requires low(sep) -// @ ensures low(res) // SIF: turn in to low(sep) ==> low(res) eventually maybe +// @ requires low(as_) && low(sep) +// @ ensures low(res) // @ decreases func fmtAS(as_ AS, sep string) (res string) { if !as_.inRange() { - // SIF: See Gobra issue #835 + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(as_) //@ assert low(MaxAS) //@ ghost v := []interface{}{as_, MaxAS} @@ -140,11 +138,7 @@ func fmtAS(as_ AS, sep string) (res string) { var b /*@@@*/ strings.Builder // @ b.ZeroBuilderIsReadyToUse() b.Grow(maxLen /*@, true @*/) - // SIF: While I do think assigning low(sep) to a ghost variable would make - // sense (here), at the moment it is simply replaced by `:= true` - //@ ghost isLowB := true - //@ ghost isLowSep := low(sep) - // @ invariant acc(b.Mem(), 1/2) && acc(b.LowMem(isLowB), 1/2) + // @ invariant acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) // @ invariant low(i) // @ decreases asParts - i for i := 0; i < asParts; i++ { diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 8dfa385f4..66c2c61ac 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -109,10 +109,7 @@ type HostAddr interface { //@ decreases Copy() (res HostAddr) - // SIF: I wanted to introduce an assertion `LowEqual(HostAddr)`, but that - // led to a strange exception. As every implementation of `Equal` needs to - // cast `o` anyway, I think it's fine to assert `low(typeOf(o))` directly. - //@ requires low(typeOf(o)) + //@ requires low(typeOf(o)) //@ preserves acc(Mem(), R13) && acc(o.Mem(), R13) //@ decreases Equal(o HostAddr) bool @@ -163,7 +160,7 @@ func (h HostNone) Copy() (res HostAddr) { return tmp } -// SIF: The Viper encoding contains a non-low branch condition if not `low(typeOf(o))` +// The Viper encoding branches on `typeOf(o)`. // @ requires low(typeOf(o)) // @ ensures res == (typeOf(o) == type[HostNone]) // @ decreases @@ -240,10 +237,10 @@ func (h HostIPv4) Equal(o HostAddr) bool { // @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv4) String() string { - //@ assert unfolding acc(h.Mem(), R13/2) in len(h) == HostLenIPv4 //@ unfold acc(h.Mem(), R13/2) //@ unfold acc(h.LowMem(), R13/2) //@ fold acc(h.Mem(), R13) + //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) return h.IP().String() @@ -313,10 +310,10 @@ func (h HostIPv6) Equal(o HostAddr) bool { // @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv6) String() string { - //@ assert unfolding acc(h.Mem(), R13/2) in len(h) == HostLenIPv6 //@ unfold acc(h.Mem(), R13/2) //@ unfold acc(h.LowMem(), R13/2) //@ fold acc(h.Mem(), R13) + //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv6 //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) return h.IP().String() @@ -425,7 +422,7 @@ func (h HostSVC) String() string { if h.IsMulticast() { cast = 'M' } - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(name) //@ assert low(cast) //@ assert low(uint16(h)) diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index 9992cc040..8ff12a782 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -23,15 +23,12 @@ import ( "github.com/scionproto/scion/verification/utils/slices" ) -// SIF: Asserting h.Mem() in LowMem doesn't work well. - pred (h HostNone) Mem() { len(h) == HostLenNone } pred (h HostNone) LowMem() { true } HostNone implements HostAddr { (h HostNone) String() (str string) { unfold acc(h.Mem(), R13/2) - unfold acc(h.LowMem(), R13/2) str = h.String() fold acc(h.Mem(), R13) } @@ -58,8 +55,6 @@ pred (h HostSVC) LowMem() { low(h) } HostSVC implements HostAddr { (h HostSVC) String() (str string) { - unfold acc(h.Mem(), R13/2) - unfold acc(h.LowMem(), R13/2) str = h.String() fold acc(h.Mem(), R13) } diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 1d4f7951c..f6433a398 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -50,7 +50,7 @@ type ISD uint16 // ParseISD parses an ISD from a decimal string. Note that ISD 0 is parsed // without any errors. // @ requires low(s) -// @ ensures low(retISD) && low(retErr) +// @ ensures low(retISD) && low(retErr != nil) // @ decreases func ParseISD(s string) (retISD ISD, retErr error) { isd, err := strconv.ParseUint(s, 10, ISDBits) @@ -94,7 +94,7 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { } if len(parts) != asParts { - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(sep) //@ assert low(_as) //@ ghost errCtx := []interface{}{"sep", sep, "value", _as} @@ -103,14 +103,15 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { } var parsed AS //@ invariant 0 <= i && i <= asParts - //@ invariant forall i int :: { &parts[i] } 0 <= i && i < len(parts) ==> acc(&parts[i]) && low(parts[i]) + //@ invariant acc(parts) + //@ invariant forall i int :: { parts[i] } 0 <= i && i < len(parts) ==> low(parts[i]) //@ invariant low(i) && low(_as) && low(parsed) //@ decreases asParts - i for i := 0; i < asParts; i++ { parsed <<= asPartBits v, err := strconv.ParseUint(parts[i], asPartBase, asPartBits) if err != nil { - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(i) //@ assert low(_as) //@ ghost errCtx := []interface{}{"index", i, "value", _as} @@ -123,7 +124,7 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { // against future refactor mistakes. if !parsed.inRange() { // (VerifiedSCION) Added cast around MaxAS to be able to call serrors.New - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(uint64(MaxAS)) //@ assert low(_as) //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", _as} @@ -135,7 +136,7 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { // @ requires low(s) // @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr) +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func asParseBGP(s string) (retAs AS, retErr error) { _as, err := strconv.ParseUint(s, 10, BGPASBits) @@ -163,10 +164,9 @@ func (_as AS) String() string { return fmtAS(_as, ":") } -// SIF: For pure functions, we can automatically deduce low(in) ==> low(out) // @ decreases // @ pure -func (_as AS) inRange() (res bool) { +func (_as AS) inRange() bool { return _as <= MaxAS } @@ -175,7 +175,7 @@ func (_as AS) inRange() (res bool) { func (_as AS) MarshalText() ([]byte, error) { if !_as.inRange() { // (VerifiedSCION) Added cast around MaxAS and as to be able to call serrors.New - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(uint64(MaxAS)) //@ assert low(uint64(_as)) //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", uint64(_as)} @@ -192,7 +192,6 @@ func (_as AS) MarshalText() ([]byte, error) { // @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases func (_as *AS) UnmarshalText(text []byte) error { - // SIF: See Gobra issue #832 //@ sif.LowSliceImpliesLowString(text, 1/1) parsed, err := ParseAS(string(text)) if err != nil { @@ -245,7 +244,7 @@ func IAFrom(isd ISD, _as AS) (ia IA, err error) { func ParseIA(ia string) (retIA IA, retErr error) { parts := strings.Split(ia, "-") if len(parts) != 2 { - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ assert low(ia) //@ ghost errCtx := []interface{}{"value", ia} //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) @@ -262,16 +261,15 @@ func ParseIA(ia string) (retIA IA, retErr error) { return MustIAFrom(isd, _as), nil } -// SIF: pure implies low(in) ==> low(out) // @ decreases // @ pure -func (ia IA) ISD() (res ISD) { +func (ia IA) ISD() ISD { return ISD(ia >> ASBits) } // @ decreases // @ pure -func (ia IA) AS() (res AS) { +func (ia IA) AS() AS { return AS(ia) & MaxAS } @@ -288,7 +286,6 @@ func (ia IA) MarshalText() ([]byte, error) { // @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases func (ia *IA) UnmarshalText(b []byte) error { - // SIF: See Gobra issue #832 //@ sif.LowSliceImpliesLowString(b, 1/1) parsed, err := ParseIA(string(b)) if err != nil { @@ -310,20 +307,18 @@ func (ia IA) Equal(other IA) bool { } // IsWildcard returns whether the ia has a wildcard part (isd or as). -// SIF: Required due to short circuit evaluation. +// Due to short-circuit evaluation, this branches on `ia.ISD() == 0`. // @ requires low(ia) // @ decreases func (ia IA) IsWildcard() bool { return ia.ISD() == 0 || ia.AS() == 0 } -// SIF: ATM `Sprintf` requires all arguments to be low, regardless of -// whether we need the output to be low. // @ requires low(ia) // @ decreases func (ia IA) String() string { // (VerifiedSCION) Added casts around ia.ISD() and ia.AS() to be able to pass them to 'fmt.Sprintf' - // SIF: See Gobra issue #835 for why this assumption is currently necessary + // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ ghost v := []interface{}{ia.ISD(), ia.AS()} //@ assert low(v[0]) //@ assert low(v[1]) diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 11e03a815..f3b443063 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -20,24 +20,15 @@ import ( "fmt" "encoding" "flag" - - "github.com/scionproto/scion/verification/utils/sif" ) pred (ia *IA) Mem() { acc(ia) } pred (ia *IA) LowMem() { acc(ia) && low(*ia) } -pred (ia *IA) LowSet(s string) { low(s) } - -pred (*IA) LowUnmarshalText(text []byte) { - low(len(text)) && - forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) && low(text[i]) -} (*IA) implements encoding.TextUnmarshaler { (ia *IA) UnmarshalText(text []byte) (err error) { unfold ia.Mem() - unfold ia.LowUnmarshalText(text) err = ia.UnmarshalText(text) fold ia.Mem() } @@ -60,15 +51,10 @@ IA implements fmt.Stringer { } pred (_as *AS) Mem() { acc(_as) } -pred (*AS) LowUnmarshalText(text []byte) { - low(len(text)) && - forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) && low(text[i]) -} (*AS) implements encoding.TextUnmarshaler { (_as *AS) UnmarshalText(text []byte) (err error) { unfold _as.Mem() - unfold _as.LowUnmarshalText(text) err = _as.UnmarshalText(text) fold _as.Mem() } @@ -84,7 +70,6 @@ pred (*AS) LowUnmarshalText(text []byte) { (ia *IA) Set(s string) (err error) { unfold ia.Mem() - unfold ia.LowSet(s) err = ia.Set(s) fold ia.Mem() } diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 24712fbf0..7b4f019eb 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,16 +76,18 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// SIF: See verification/utils/fmt/fmt.gobra for concerns regarding this spec. -requires low(msg) && low(cause) && low(len(errCtx)) +requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) requires cause != nil ==> cause.ErrorMem() -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) && low(errCtx[i]) +// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` +// using a pure Boolean function. +requires low(msg) && low(cause) && low(len(errCtx)) && + forall i int :: { errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. // requires forall i int :: 0 <= i && i < len(errCtx) ==> IsOfPrimitiveType(errCtx[i]) ensures res != nil && res.ErrorMem() ensures cause != nil ==> (res.ErrorMem() --* cause.ErrorMem()) -ensures low(res) +ensures low(res != nil) decreases func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) @@ -98,7 +100,7 @@ requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCt // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` // using a pure Boolean function. requires low(msg) && low(len(errCtx)) && - forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) + forall i int :: { errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index 768798c02..b27fd5518 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -18,13 +18,14 @@ package encoding type TextUnmarshaler interface { pred Mem() - - pred LowUnmarshalText([]byte) - // SIF: Needed e.g. for `(*AS), (*IA).UnmarshalText` - requires LowUnmarshalText(text) - preserves Mem() - ensures acc(text) + // TODO: It might make sense to abstract "low(text)" using either a predicate + // or a pure Boolean function, as not every implementation might require it. + requires acc(text) + requires low(len(text)) && + forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) + preserves Mem() && acc(text) + ensures acc(text) decreases UnmarshalText(text []byte) error } diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index d109dc5a8..0ee0c50a7 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -18,19 +18,16 @@ package flag type Value interface { pred Mem() - pred LowMem() // SIF: allows specifying sensitivity (preconditions) + pred LowMem() - // SIF: See `test10.gobra` for why I do it this way ATM. requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) ensures acc(Mem()) decreases String() string - pred LowSet(s string) - - // SIF: This is necessary e.g. for `*IA` bc. `(*IA).Set` uses `s` in a - // branch condition. - requires LowSet(s) + // TODO: It might make sense to abstract "low(s)" using either a predicate + // or a pure Boolean function, as not every implementation might require it. + requires low(s) preserves acc(Mem()) decreases Set(s string) error diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 203bff71b..c37809e75 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -31,7 +31,7 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() - pred LowMem() // SIF: allows specifying sensitivity (preconditions) + pred LowMem() requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) ensures acc(Mem()) diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 2ef9335f5..ca4f3f2cb 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -145,12 +145,6 @@ pure func (ip IP) Equal(x IP) bool // ParseIP parses s as an IP address, returning the result. ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len -// SIF: Although `low(res)` would suffice to assert both of this, I don't think -// it would be appropriate here. `low(res)` asserts that the returned references -// are equal, which I don't think is sensible (every caller should get a new -// slice). -// TODO: Revisit this after talking to @jcp19 about what it means for two -// references to be equal in Viper. ensures low(s) ==> (low(res == nil) && low(len(res))) decreases _ func ParseIP(s string) (res IP) diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 021d86c5a..556a478b7 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -27,6 +27,7 @@ decreases _ func (b *Builder) String(ghost isLow bool) (str string) requires 0 <= n +requires isLow ==> low(n) preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) decreases _ func (b *Builder) Grow(n int, ghost isLow bool) From 0b6ec9c6d47134a5e633f08e42dcde0e5eab20ab Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 21:22:30 +0200 Subject: [PATCH 046/104] Use abstract pure Boolean functions instead of `LowMem` etc. --- pkg/addr/fmt.go | 10 ++-- pkg/addr/host.go | 22 ++++----- pkg/addr/host_spec.gobra | 49 ++++++++++++------- pkg/addr/isdas.go | 5 +- pkg/addr/isdas_spec.gobra | 35 +++++++++---- verification/dependencies/flag/flag.gobra | 10 ++-- verification/dependencies/fmt/fmt.gobra | 10 ++-- .../dependencies/strings/builder.gobra | 29 ++++++----- 8 files changed, 105 insertions(+), 65 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index 4dedd0506..dfc32048c 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -137,21 +137,21 @@ func fmtAS(as_ AS, sep string) (res string) { var maxLen = len("ffff:ffff:ffff") var b /*@@@*/ strings.Builder // @ b.ZeroBuilderIsReadyToUse() - b.Grow(maxLen /*@, true @*/) - // @ invariant acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) + b.Grow(maxLen) + // @ invariant b.Mem() && b.IsLow() // @ invariant low(i) // @ decreases asParts - i for i := 0; i < asParts; i++ { if i > 0 { - b.WriteString(sep /*@, true, true @*/) + b.WriteString(sep) } shift := uint(asPartBits * (asParts - i - 1)) // (VerifiedSCION) the following property is guaranteed by the type system, // but Gobra cannot infer it yet // @ assume 0 <= uint64(as_>>shift)&asPartMask - b.WriteString(strconv.FormatUint(uint64(as_>>shift)&asPartMask, asPartBase) /*@, true, true @*/) + b.WriteString(strconv.FormatUint(uint64(as_>>shift)&asPartMask, asPartBase)) } - ret := b.String( /*@ true @*/ ) + ret := b.String() return ret } diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 66c2c61ac..4c998409a 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -84,7 +84,6 @@ const ( type HostAddr interface { //@ pred Mem() - //@ pred LowMem() //@ preserves acc(Mem(), R13) //@ decreases @@ -119,7 +118,12 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer - //@ requires acc(Mem(), R13/2) && acc(LowMem(), R13/2) + //@ ghost + //@ requires Mem() + //@ decreases + //@ pure IsLow() bool + + //@ requires acc(Mem(), R13) && IsLow() //@ ensures acc(Mem(), R13) //@ decreases String() string @@ -233,13 +237,10 @@ func (h HostIPv4) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ requires acc(h.Mem(), R13/2) && acc(h.LowMem(), R13/2) +// @ requires acc(h.Mem(), R13) // @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv4) String() string { - //@ unfold acc(h.Mem(), R13/2) - //@ unfold acc(h.LowMem(), R13/2) - //@ fold acc(h.Mem(), R13) //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) @@ -306,13 +307,10 @@ func (h HostIPv6) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ requires acc(h.Mem(), R13/2) && acc(h.LowMem(), R13/2) +// @ requires acc(h.Mem(), R13) // @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv6) String() string { - //@ unfold acc(h.Mem(), R13/2) - //@ unfold acc(h.LowMem(), R13/2) - //@ fold acc(h.Mem(), R13) //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv6 //@ ghost defer fold acc(h.Mem(), R13) //@ ghost defer fold acc(sl.Bytes(h, 0, len(h)), R13) @@ -414,9 +412,11 @@ func (h HostSVC) Equal(o HostAddr) bool { return ok && h == ha } -// @ requires low(h) +// @ requires acc(h.Mem(), R13) && h.IsLow() +// @ ensures acc(h.Mem(), R13) // @ decreases func (h HostSVC) String() string { + //@ h.RevealIsLow(R13) name := h.BaseString() cast := 'A' if h.IsMulticast() { diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index 8ff12a782..de3c2adf1 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -24,21 +24,23 @@ import ( ) pred (h HostNone) Mem() { len(h) == HostLenNone } -pred (h HostNone) LowMem() { true } - -HostNone implements HostAddr { - (h HostNone) String() (str string) { - unfold acc(h.Mem(), R13/2) - str = h.String() - fold acc(h.Mem(), R13) - } -} + +ghost +requires h.Mem() +decreases +pure func (h HostNone) IsLow() bool + +HostNone implements HostAddr pred (h HostIPv4) Mem() { len(h) == HostLenIPv4 && slices.Bytes(h, 0, len(h)) } -pred (h HostIPv4) LowMem() { slices.Bytes(h, 0, len(h)) } + +ghost +requires h.Mem() +decreases +pure func (h HostIPv4) IsLow() bool HostIPv4 implements HostAddr @@ -46,22 +48,31 @@ pred (h HostIPv6) Mem() { len(h) == HostLenIPv6 && slices.Bytes(h, 0, len(h)) } -pred (h HostIPv6) LowMem() { slices.Bytes(h, 0, len(h)) } + +ghost +requires h.Mem() +decreases +pure func (h HostIPv6) IsLow() bool HostIPv6 implements HostAddr pred (h HostSVC) Mem() { true } -pred (h HostSVC) LowMem() { low(h) } -HostSVC implements HostAddr { - (h HostSVC) String() (str string) { - str = h.String() - fold acc(h.Mem(), R13) - } -} +ghost +requires h.Mem() +decreases +pure func (h HostSVC) IsLow() bool + +ghost +requires p > 0 +requires acc(h.Mem(), p) && h.IsLow() +ensures acc(h.Mem(), p) && low(h) +decreases +func (h HostSVC) RevealIsLow(p perm) + +HostSVC implements HostAddr pred (h *HostSVC) Mem() { acc(h) } -pred (h *HostSVC) LowMem() { acc(h) && low(*h) } (*HostSVC) implements HostAddr diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index f6433a398..caeee5d6a 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -273,7 +273,7 @@ func (ia IA) AS() AS { return AS(ia) & MaxAS } -// @ requires low(ia) +// @ requires ia.IsLow() // @ decreases func (ia IA) MarshalText() ([]byte, error) { return []byte(ia.String()), nil @@ -314,9 +314,10 @@ func (ia IA) IsWildcard() bool { return ia.ISD() == 0 || ia.AS() == 0 } -// @ requires low(ia) +// @ requires ia.IsLow() // @ decreases func (ia IA) String() string { + //@ ia.RevealIsLow() // (VerifiedSCION) Added casts around ia.ISD() and ia.AS() to be able to pass them to 'fmt.Sprintf' // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. //@ ghost v := []interface{}{ia.ISD(), ia.AS()} diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index f3b443063..8a31cabfb 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -24,7 +24,12 @@ import ( pred (ia *IA) Mem() { acc(ia) } -pred (ia *IA) LowMem() { acc(ia) && low(*ia) } +ghost +requires ia.Mem() +decreases +pure func (ia *IA) IsLowForValue() bool { + return unfolding ia.Mem() in ia.IsLow() +} (*IA) implements encoding.TextUnmarshaler { (ia *IA) UnmarshalText(text []byte) (err error) { @@ -37,16 +42,29 @@ pred (ia *IA) LowMem() { acc(ia) && low(*ia) } // Implementation proof would confuse the two predicates named Mem for IA and *IA // Issue: https://github.com/viperproject/gobra/issues/449 pred MemForStringer(ia IA) { true } -pred LowMemForStringer(ia IA) { low(ia) } + +ghost +decreases +pure func (ia IA) IsLow() bool + +ghost +requires MemForStringer(ia) +decreases +pure func (ia IA) IsLowForStringer() bool { + return ia.IsLow() +} + +ghost +requires ia.IsLow() +ensures low(ia) +decreases +func (ia IA) RevealIsLow() IA implements fmt.Stringer { pred Mem := MemForStringer - pred LowMem := LowMemForStringer - (ia IA) String() (str string) { - unfold acc(LowMemForStringer(ia), 1/2) - str = ia.String() - fold acc(MemForStringer(ia)) + (ia IA) String() string { + return ia.String() } } @@ -62,8 +80,7 @@ pred (_as *AS) Mem() { acc(_as) } (*IA) implements flag.Value { (ia *IA) String() (str string) { - unfold acc(ia.Mem(), 1/2) - unfold acc(ia.LowMem(), 1/2) + unfold ia.Mem() str = ia.String() fold ia.Mem() } diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 0ee0c50a7..8dda63e09 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -18,10 +18,14 @@ package flag type Value interface { pred Mem() - pred LowMem() + + ghost + requires Mem() + decreases + pure IsLowForValue() bool - requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) - ensures acc(Mem()) + requires Mem() && IsLowForValue() + ensures Mem() decreases String() string diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index c37809e75..39f7c28e2 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -31,10 +31,14 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() - pred LowMem() - requires acc(Mem(), 1/2) && acc(LowMem(), 1/2) - ensures acc(Mem()) + ghost + requires Mem() + decreases + pure IsLowForStringer() bool + + requires Mem() && IsLowForStringer() + ensures Mem() decreases String() string } diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 556a478b7..379f15a40 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -18,31 +18,34 @@ type Builder struct { } pred (b *Builder) Mem() -pred (b *Builder) LowMem(ghost isLow bool) + +ghost +requires b.Mem() +decreases +pure func (b *Builder) IsLow() bool // String returns the accumulated string. -preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) -ensures isLow ==> low(str) +preserves b.Mem() +ensures old(b.IsLow()) == b.IsLow() +ensures b.IsLow() ==> low(str) decreases _ -func (b *Builder) String(ghost isLow bool) (str string) +func (b *Builder) String() (str string) requires 0 <= n -requires isLow ==> low(n) -preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) +preserves b.Mem() +ensures old(b.IsLow()) && low(n) ==> b.IsLow() decreases _ -func (b *Builder) Grow(n int, ghost isLow bool) +func (b *Builder) Grow(n int) -requires acc(b.Mem(), 1/2) && acc(b.LowMem(isLowB), 1/2) -requires isLowS ==> low(s) +preserves b.Mem() ensures err == nil -ensures acc(b.Mem(), 1/2) && acc(b.LowMem(isLowS && isLowB), 1/2) -ensures isLowS && isLowB ==> low(n) +ensures low(s) && old(b.IsLow()) ==> b.IsLow() && low(n) decreases _ -func (b *Builder) WriteString(s string, ghost isLowB, isLowS bool) (n int, err error) +func (b *Builder) WriteString(s string) (n int, err error) ghost requires acc(b) requires *b === Builder{} -ensures acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) +ensures b.Mem() && b.IsLow() decreases _ func (b *Builder) ZeroBuilderIsReadyToUse() \ No newline at end of file From d5f94cb14690f4c55f258a4e14c49a5a52f67587 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 21:54:51 +0200 Subject: [PATCH 047/104] Clean up --- pkg/addr/fmt.go | 7 +++---- pkg/addr/host.go | 10 ++++++---- pkg/addr/host_spec.gobra | 3 ++- pkg/addr/isdas_spec.gobra | 6 ++++++ verification/dependencies/encoding/encoding.gobra | 2 +- verification/dependencies/flag/flag.gobra | 4 ++++ verification/dependencies/fmt/fmt.gobra | 4 ++++ verification/dependencies/strings/builder.gobra | 1 + 8 files changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index dfc32048c..d1b634d2d 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -138,8 +138,8 @@ func fmtAS(as_ AS, sep string) (res string) { var b /*@@@*/ strings.Builder // @ b.ZeroBuilderIsReadyToUse() b.Grow(maxLen) - // @ invariant b.Mem() && b.IsLow() - // @ invariant low(i) + // @ invariant b.Mem() + // @ invariant low(i) && b.IsLow() // @ decreases asParts - i for i := 0; i < asParts; i++ { if i > 0 { @@ -151,8 +151,7 @@ func fmtAS(as_ AS, sep string) (res string) { // @ assume 0 <= uint64(as_>>shift)&asPartMask b.WriteString(strconv.FormatUint(uint64(as_>>shift)&asPartMask, asPartBase)) } - ret := b.String() - return ret + return b.String() } // (VerifiedSCION) revert this change when Gobra is fixed. diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 4c998409a..4b24be153 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -118,6 +118,10 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer + // Return whether the underlying data (or at least the data relevant for + // 'String') is low. + // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; + // at the moment, this can just be an abstract function. //@ ghost //@ requires Mem() //@ decreases @@ -237,8 +241,7 @@ func (h HostIPv4) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ requires acc(h.Mem(), R13) -// @ ensures acc(h.Mem(), R13) +// @ preserves acc(h.Mem(), R13) // @ decreases func (h HostIPv4) String() string { //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 @@ -307,8 +310,7 @@ func (h HostIPv6) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ requires acc(h.Mem(), R13) -// @ ensures acc(h.Mem(), R13) +// @ preserves acc(h.Mem(), R13) // @ decreases func (h HostIPv6) String() string { //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv6 diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index de3c2adf1..d5bc13238 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -19,7 +19,6 @@ package addr import ( "net" - . "github.com/scionproto/scion/verification/utils/definitions" "github.com/scionproto/scion/verification/utils/slices" ) @@ -63,6 +62,8 @@ requires h.Mem() decreases pure func (h HostSVC) IsLow() bool +// "Reveal the body" of `h.IsLow` (necessary bc. we can't implement `h.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, remove this. ghost requires p > 0 requires acc(h.Mem(), p) && h.IsLow() diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 8a31cabfb..99f54e5b8 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -43,6 +43,10 @@ pure func (ia *IA) IsLowForValue() bool { // Issue: https://github.com/viperproject/gobra/issues/449 pred MemForStringer(ia IA) { true } +// Both `IA` (for `fmt.Stringer`) and `*IA` (for `flag.Value`) implement the same +// `String` function, which needs the underlying value to be low. +// Sharing `IsLow` doesn't work, however, thus `IA` implements `IsLowForStringer` +// and `*IA` implements `IsLowForValue`, which both just resolve to this `IsLow`. ghost decreases pure func (ia IA) IsLow() bool @@ -54,6 +58,8 @@ pure func (ia IA) IsLowForStringer() bool { return ia.IsLow() } +// "Reveal" the "body" of `ia.IsLow` (necessary bc. we can't implement `ia.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, remove this. ghost requires ia.IsLow() ensures low(ia) diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index b27fd5518..544e39009 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -24,7 +24,7 @@ type TextUnmarshaler interface { requires acc(text) requires low(len(text)) && forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) - preserves Mem() && acc(text) + preserves Mem() ensures acc(text) decreases UnmarshalText(text []byte) error diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 8dda63e09..9287dbfe6 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -19,6 +19,10 @@ package flag type Value interface { pred Mem() + // Return whether the underlying data (or at least the data relevant for + // 'String') is low. + // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; + // at the moment, this can just be an abstract function. ghost requires Mem() decreases diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 39f7c28e2..193b2ea53 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -32,6 +32,10 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() + // Return whether the underlying data (or at least the data relevant for + // 'String') is low. + // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; + // at the moment, this can just be an abstract function. ghost requires Mem() decreases diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 379f15a40..14c238a08 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -19,6 +19,7 @@ type Builder struct { pred (b *Builder) Mem() +// Return whether the underlying data is low. ghost requires b.Mem() decreases From ca4cf53e2839951d111bf6176cbd10dfc6215e2b Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 14 Apr 2025 21:57:39 +0200 Subject: [PATCH 048/104] Fix contract of `TextUnmarshaler.UnmarshalText` --- verification/dependencies/encoding/encoding.gobra | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index b27fd5518..544e39009 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -24,7 +24,7 @@ type TextUnmarshaler interface { requires acc(text) requires low(len(text)) && forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) - preserves Mem() && acc(text) + preserves Mem() ensures acc(text) decreases UnmarshalText(text []byte) error From 32f8f6fa2bed02309e5442c8f949399398c18ac9 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 19 Jun 2025 00:26:21 +0200 Subject: [PATCH 049/104] Address feedback --- pkg/addr/host.go | 6 ++---- pkg/addr/host_spec.gobra | 11 ++++++++++- pkg/addr/isdas_spec.gobra | 5 ++++- verification/dependencies/flag/flag.gobra | 2 -- verification/dependencies/fmt/fmt.gobra | 2 -- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 4b24be153..f48e8dabb 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -118,10 +118,8 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer - // Return whether the underlying data (or at least the data relevant for - // 'String') is low. - // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; - // at the moment, this can just be an abstract function. + // Return whether all the underlying data used in the computation of + // `String` is low. //@ ghost //@ requires Mem() //@ decreases diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index d5bc13238..92f7f8569 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -24,7 +24,9 @@ import ( pred (h HostNone) Mem() { len(h) == HostLenNone } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost +trusted requires h.Mem() decreases pure func (h HostNone) IsLow() bool @@ -36,7 +38,9 @@ pred (h HostIPv4) Mem() { slices.Bytes(h, 0, len(h)) } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost +trusted requires h.Mem() decreases pure func (h HostIPv4) IsLow() bool @@ -48,7 +52,9 @@ pred (h HostIPv6) Mem() { slices.Bytes(h, 0, len(h)) } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost +trusted requires h.Mem() decreases pure func (h HostIPv6) IsLow() bool @@ -57,14 +63,17 @@ HostIPv6 implements HostAddr pred (h HostSVC) Mem() { true } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost +trusted requires h.Mem() decreases pure func (h HostSVC) IsLow() bool // "Reveal the body" of `h.IsLow` (necessary bc. we can't implement `h.IsLow` yet). -// TODO: Once Gobra issue #846 is resolved, remove this. +// TODO: Once Gobra issue #846 is resolved, implement this. ghost +trusted requires p > 0 requires acc(h.Mem(), p) && h.IsLow() ensures acc(h.Mem(), p) && low(h) diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 99f54e5b8..8c0054683 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -47,7 +47,9 @@ pred MemForStringer(ia IA) { true } // `String` function, which needs the underlying value to be low. // Sharing `IsLow` doesn't work, however, thus `IA` implements `IsLowForStringer` // and `*IA` implements `IsLowForValue`, which both just resolve to this `IsLow`. +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost +trusted decreases pure func (ia IA) IsLow() bool @@ -59,8 +61,9 @@ pure func (ia IA) IsLowForStringer() bool { } // "Reveal" the "body" of `ia.IsLow` (necessary bc. we can't implement `ia.IsLow` yet). -// TODO: Once Gobra issue #846 is resolved, remove this. +// TODO: Once Gobra issue #846 is resolved, implement this. ghost +trusted requires ia.IsLow() ensures low(ia) decreases diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 9287dbfe6..33f855b94 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -21,8 +21,6 @@ type Value interface { // Return whether the underlying data (or at least the data relevant for // 'String') is low. - // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; - // at the moment, this can just be an abstract function. ghost requires Mem() decreases diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 193b2ea53..b0ecac5e3 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -34,8 +34,6 @@ type Stringer interface { // Return whether the underlying data (or at least the data relevant for // 'String') is low. - // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`; - // at the moment, this can just be an abstract function. ghost requires Mem() decreases From 6c50035005c11c9d70b23cbd71c454ca9517d61e Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 19 Jun 2025 00:35:38 +0200 Subject: [PATCH 050/104] Address missed feedback --- verification/dependencies/flag/flag.gobra | 4 ++-- verification/dependencies/fmt/fmt.gobra | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 33f855b94..b59daa0c7 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -19,8 +19,8 @@ package flag type Value interface { pred Mem() - // Return whether the underlying data (or at least the data relevant for - // 'String') is low. + // Return whether all the underlying data used in the computation of + // `String` is low. ghost requires Mem() decreases diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index b0ecac5e3..fa7cc9547 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -32,8 +32,8 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() - // Return whether the underlying data (or at least the data relevant for - // 'String') is low. + // Return whether all the underlying data used in the computation of + // `String` is low. ghost requires Mem() decreases From da722cb60f3a7e395400c710a90c62e18d05e259 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 26 Jun 2025 16:13:55 +0200 Subject: [PATCH 051/104] Minor style adjustments --- pkg/addr/fmt.go | 8 ++++---- verification/dependencies/encoding/encoding.gobra | 2 -- verification/dependencies/flag/flag.gobra | 2 -- verification/dependencies/strings/builder.gobra | 4 ++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index 4dedd0506..35a2f413f 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -118,10 +118,10 @@ func FormatAS(as_ AS, opts ...FormatOption) string { func fmtAS(as_ AS, sep string) (res string) { if !as_.inRange() { // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(as_) - //@ assert low(MaxAS) - //@ ghost v := []interface{}{as_, MaxAS} - //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) + // @ assert low(as_) + // @ assert low(MaxAS) + // @ ghost v := []interface{}{as_, MaxAS} + // @ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%d [Illegal AS: larger than %d]", as_, MaxAS) } // Format BGP ASes as decimal diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index 544e39009..e801cd559 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -19,8 +19,6 @@ package encoding type TextUnmarshaler interface { pred Mem() - // TODO: It might make sense to abstract "low(text)" using either a predicate - // or a pure Boolean function, as not every implementation might require it. requires acc(text) requires low(len(text)) && forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 0ee0c50a7..22128f5b3 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -25,8 +25,6 @@ type Value interface { decreases String() string - // TODO: It might make sense to abstract "low(s)" using either a predicate - // or a pure Boolean function, as not every implementation might require it. requires low(s) preserves acc(Mem()) decreases diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 556a478b7..ae66c5b3b 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -22,7 +22,7 @@ pred (b *Builder) LowMem(ghost isLow bool) // String returns the accumulated string. preserves acc(b.Mem(), 1/2) && acc(b.LowMem(isLow), 1/2) -ensures isLow ==> low(str) +ensures isLow ==> low(str) decreases _ func (b *Builder) String(ghost isLow bool) (str string) @@ -43,6 +43,6 @@ func (b *Builder) WriteString(s string, ghost isLowB, isLowS bool) (n int, err e ghost requires acc(b) requires *b === Builder{} -ensures acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) +ensures acc(b.Mem(), 1/2) && acc(b.LowMem(true), 1/2) decreases _ func (b *Builder) ZeroBuilderIsReadyToUse() \ No newline at end of file From 79f7d31504b5ddc5f4adf43682381381b13fa7c1 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 26 Jun 2025 16:41:26 +0200 Subject: [PATCH 052/104] Make verification/dependencies/net verify --- pkg/addr/host.go | 3 ++- verification/dependencies/net/ip.gobra | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index c733aa5ba..55db5eb1d 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -503,9 +503,10 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { } } -// @ requires low(len(ip)) // @ requires acc(ip) // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 +// @ requires low(len(ip)) && (len(ip) == HostLenIPv6 ==> +// @ low(net.isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) // @ ensures res.Mem() // @ decreases func HostFromIP(ip net.IP) (res HostAddr) { diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index ca4f3f2cb..3f6dad638 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -61,9 +61,12 @@ preserves forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R15) func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. -requires low(len(ip)) -preserves wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) -preserves !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) +requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) +requires !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) +requires low(len(ip)) && (len(ip) == IPv6len ==> + low(isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) +ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) +ensures !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len ensures len(ip) == IPv4len ==> ip === res ensures (len(ip) == IPv6len && isZeros(ip[0:10]) && ip[10] == 255 && ip[11] == 255) ==> res != nil @@ -95,6 +98,7 @@ decreases pure func isZeros(s []byte) bool // To16 converts the IP address ip to a 16-byte representation. +requires low(len(ip)) preserves forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R15) ensures len(ip) == IPv4len ==> (len(res) == IPv4len && @@ -145,6 +149,8 @@ pure func (ip IP) Equal(x IP) bool // ParseIP parses s as an IP address, returning the result. ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len -ensures low(s) ==> (low(res == nil) && low(len(res))) +ensures low(s) ==> + (low(res == nil) && low(len(res)) && (len(res) == IPv6len ==> + low(isZeros(res[0:10])) && low(res[10] == 255) && low(res[11] == 255))) decreases _ func ParseIP(s string) (res IP) From 6edbb0c9636f427bb6ca97433eada6f4894116bb Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 26 Jun 2025 22:53:47 +0200 Subject: [PATCH 053/104] Minor style adjustments --- verification/dependencies/strings/builder.gobra | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 14c238a08..917998d54 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -21,6 +21,7 @@ pred (b *Builder) Mem() // Return whether the underlying data is low. ghost +trusted requires b.Mem() decreases pure func (b *Builder) IsLow() bool @@ -47,6 +48,6 @@ func (b *Builder) WriteString(s string) (n int, err error) ghost requires acc(b) requires *b === Builder{} -ensures b.Mem() && b.IsLow() +ensures b.Mem() && b.IsLow() decreases _ func (b *Builder) ZeroBuilderIsReadyToUse() \ No newline at end of file From 3b3762c109ad95b44df22e1e8abcb0086b5c33e1 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 1 Jul 2025 18:22:37 +0200 Subject: [PATCH 054/104] Explain need for longer names for `IsLow` ... in `fmt.Stringer` and `flag.Value` --- pkg/addr/isdas_spec.gobra | 4 ---- verification/dependencies/flag/flag.gobra | 5 +++++ verification/dependencies/fmt/fmt.gobra | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index 8c0054683..d41721572 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -43,10 +43,6 @@ pure func (ia *IA) IsLowForValue() bool { // Issue: https://github.com/viperproject/gobra/issues/449 pred MemForStringer(ia IA) { true } -// Both `IA` (for `fmt.Stringer`) and `*IA` (for `flag.Value`) implement the same -// `String` function, which needs the underlying value to be low. -// Sharing `IsLow` doesn't work, however, thus `IA` implements `IsLowForStringer` -// and `*IA` implements `IsLowForValue`, which both just resolve to this `IsLow`. // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 82117b648..9a74520b9 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -21,6 +21,11 @@ type Value interface { // Return whether all the underlying data used in the computation of // `String` is low. + // NOTE: The reason we don't call this function `IsLow` is that in pkg/addr, + // `IA` implements `fmt.Stringer`, and `*IA` implements `flag.Value` - which + // both require a function like `IsLow`. Currently, Gobra does not allow + // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. + // Consequently, we require separate definitions for each interface. ghost requires Mem() decreases diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index fa7cc9547..0603c444d 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -34,6 +34,11 @@ type Stringer interface { // Return whether all the underlying data used in the computation of // `String` is low. + // NOTE: The reason we don't call this function `IsLow` is that in pkg/addr, + // `IA` implements `fmt.Stringer`, and `*IA` implements `flag.Value` - which + // both require a function like `IsLow`. Currently, Gobra does not allow + // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. + // Consequently, we require separate definitions for each interface. ghost requires Mem() decreases From a19e521cd5bc84c316297c9ff8dee36275138a6f Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 11:37:09 +0200 Subject: [PATCH 055/104] Replace Low predicates with abstract IsLow functions --- private/underlay/conn/conn.go | 75 ++++---- private/underlay/conn/conn_spec.gobra | 186 +++++++++++++++----- private/underlay/sockctrl/sockopt.go | 7 +- verification/dependencies/net/udpsock.gobra | 85 ++++++--- 4 files changed, 240 insertions(+), 113 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index e6b06baca..30cd46965 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -44,7 +44,11 @@ type Messages []ipv4.Message // Conn describes the API for an underlay socket type Conn interface { //@ pred Mem() - // @ pred Low() + // pred Low() + //@ ghost + //@ requires Mem() + //@ decreases + //@ pure IsLow() bool // (VerifiedSCION) Reads a message to b. Returns the number of read bytes. //@ requires acc(Mem(), _) //@ preserves sl.Bytes(b, 0, len(b)) @@ -63,7 +67,7 @@ type Conn interface { //@ ensures err != nil ==> err.ErrorMem() Write(b []byte) (n int, err error) //@ requires acc(u.Mem(), _) - //@ requires acc(Mem(), _) && acc(Low(), _) + //@ requires acc(Mem(), _) && IsLow() //@ preserves acc(sl.Bytes(b, 0, len(b)), R10) //@ ensures err == nil ==> 0 <= n && n <= len(b) //@ ensures err != nil ==> err.ErrorMem() @@ -93,7 +97,7 @@ type Conn interface { //@ ensures err != nil ==> err.ErrorMem() //@ decreases SetDeadline(time.Time) (err error) - // @ requires acc(Mem(), 1/2) && acc(Low(), 1/2) + //@ requires Mem() && IsLow() //@ ensures err != nil ==> err.ErrorMem() //@ decreases Close() (err error) @@ -112,11 +116,11 @@ type Config struct { // New opens a new underlay socket on the specified addresses. // // The config can be used to customize socket behavior. -// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) +// @ requires cfg.Mem() // @ requires listen != nil || remote != nil -// @ requires listen != nil ==> acc(listen.Mem(), R10/2) && acc(listen.Low(), R10/2) -// @ requires remote != nil ==> acc(remote.Mem(), R10/2) && acc(remote.Low(), R10/2) -// @ requires low(listen) && low(remote) +// @ requires listen != nil ==> acc(listen.Mem(), R10) && listen.IsLow() +// @ requires remote != nil ==> acc(remote.Mem(), R10) && remote.IsLow() +// @ requires low(listen) && low(remote) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -130,11 +134,11 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { } // @ assert remote != nil ==> a == remote // @ assert remote == nil ==> a == listen - // @ unfold acc(a.Mem(), R15/2) - // @ unfold acc(a.Low(), R15/2) - // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15/2) - // @ assert forall i int :: { a.IP[i] } 0 <= i && i < len(a.IP) ==> - // @ a.IP[i] == sl.GetByte(a.IP, 0, len(a.IP), i) + // @ a.RevealIsLow(R15) + // @ assert len(a.GetIP()) == net.IPv6len ==> + // @ low(a.GetIPByte(10) == 255) && low(a.GetIPByte(11) == 255) + // @ unfold acc(a.Mem(), R15) + // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15) if a.IP.To4( /*@ false @*/ ) != nil { return newConnUDPIPv4(listen, remote, cfg) } @@ -146,10 +150,10 @@ type connUDPIPv4 struct { pconn *ipv4.PacketConn } -// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) -// @ requires listen != nil ==> acc(listen.Mem(), _) && acc(listen.Low(), _) -// @ requires remote != nil ==> acc(remote.Mem(), _) && acc(remote.Low(), _) -// @ requires low(listen) && low(remote) +// @ requires cfg.Mem() +// @ requires listen != nil ==> acc(listen.Mem(), _) && listen.IsLow() +// @ requires remote != nil ==> acc(remote.Mem(), _) && remote.IsLow() +// @ requires low(listen) && low(remote) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -221,10 +225,10 @@ type connUDPIPv6 struct { pconn *ipv6.PacketConn } -// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) -// @ requires listen != nil ==> acc(listen.Mem(), _) && acc(listen.Low(), _) -// @ requires remote != nil ==> acc(remote.Mem(), _) && acc(remote.Low(), _) -// @ requires low(listen) && low(remote) +// @ requires cfg.Mem() +// @ requires listen != nil ==> acc(listen.Mem(), _) && listen.IsLow() +// @ requires remote != nil ==> acc(remote.Mem(), _) && remote.IsLow() +// @ requires low(listen) && low(remote) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -299,13 +303,13 @@ type connUDPBase struct { } // @ requires acc(cc) -// @ requires laddr != nil ==> acc(laddr.Mem(), _) && acc(laddr.Low(), _) -// @ requires raddr != nil ==> acc(raddr.Mem(), _) && acc(raddr.Low(), _) -// @ requires acc(cfg.Mem(), 1/2) && acc(cfg.Low(), 1/2) -// @ requires low(network) && low(laddr) && low(raddr) +// @ requires laddr != nil ==> acc(laddr.Mem(), _) && laddr.IsLow() +// @ requires raddr != nil ==> acc(raddr.Mem(), _) && raddr.IsLow() +// @ requires cfg.Mem() +// @ requires low(network) && low(laddr) && low(raddr) && cfg.IsLow() // @ ensures errRet == nil ==> cc.Mem() // @ ensures errRet != nil ==> errRet.ErrorMem() -// @ ensures low(errRet != nil) +// @ ensures low(errRet != nil) // @ decreases func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cfg *Config) (errRet error) { var c *net.UDPConn @@ -352,8 +356,8 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf } } - //@ unfold acc(cfg.Mem(), 1/2) - // @ unfold acc(cfg.Low(), 1/2) + //@ cfg.RevealIsLow(perm(1)) + //@ unfold cfg.Mem() // Set and confirm send buffer size if cfg.SendBufferSize != 0 { beforeV, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_SNDBUF) @@ -494,10 +498,6 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf } } - // @ unfold acc(c.Mem(), 1/2) - // @ unfold acc(c.Low(), 1/2) - // @ fold c.Mem() - cc.conn = c cc.Listen = laddr cc.Remote = raddr @@ -527,14 +527,14 @@ func (c *connUDPBase) Write(b []byte /*@, ghost underlyingConn *net.UDPConn @*/) } // @ requires acc(dst.Mem(), _) -// @ preserves acc(c.Mem(), _) && acc(c.Low(), _) +// @ preserves acc(c.Mem(), _) && c.IsLow(true) // @ preserves unfolding acc(c.Mem(), _) in c.conn == underlyingConn // @ preserves acc(sl.Bytes(b, 0, len(b)), R15) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() func (c *connUDPBase) WriteTo(b []byte, dst *net.UDPAddr /*@, ghost underlyingConn *net.UDPConn @*/) (n int, err error) { + //@ c.RevealIsLow(true, perm(1), true) //@ unfold acc(c.Mem(), _) - //@ unfold acc(c.Low(), _) if c.Remote != nil { return c.conn.Write(b) } @@ -559,12 +559,12 @@ func (c *connUDPBase) RemoteAddr() (u *net.UDPAddr) { return c.Remote } -// @ requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +// @ requires c.Mem() && c.IsLow(true) // @ ensures err != nil ==> err.ErrorMem() // @ decreases func (c *connUDPBase) Close() (err error) { - //@ unfold acc(c.Mem(), 1/2) - // @ unfold acc(c.Low(), 1/2) + //@ c.RevealIsLow(false, perm(1), true) + //@ unfold c.Mem() if c.closed { return nil } @@ -572,6 +572,7 @@ func (c *connUDPBase) Close() (err error) { return c.conn.Close() } +// NOTE[henri]: the verification of this function seems to be very unstable // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. // @ requires 0 < n @@ -581,7 +582,7 @@ func (c *connUDPBase) Close() (err error) { // @ decreases func NewReadMessages(n int) (res Messages) { m := make(Messages, n) - // @ invariant low(i0) + //@ invariant low(i0) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() && m[j].GetAddr() == nil //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) && m[j].N == 0 //@ invariant forall j int :: { m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index aced0f91d..f7f810cfa 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -34,27 +34,70 @@ pred (c *connUDPBase) Mem() { (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } -pred (c *connUDPBase) Low() { - acc(c) && low(c.closed) && - low(c.Remote) && - c.conn.Mem() && - (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && - (c.Remote != nil ==> acc(c.Remote.Mem(), _)) -} - pred (c *connUDPBase) MemWithoutConn() { acc(c) && (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } -pred (c *connUDPBase) LowWithoutConn() { - acc(c) && low(c.closed) && - low(c.Remote) && - (c.Listen != nil ==> acc(c.Listen.Mem(), _)) && - (c.Remote != nil ==> acc(c.Remote.Mem(), _)) +ghost +requires withConn ==> c.Mem() +requires !withConn ==> c.MemWithoutConn() +decreases +pure func (c *connUDPBase) GetClosed(withConn bool) bool { + // TODO: very unsure how to format this + return withConn ? (unfolding c.Mem() in c.closed) : (unfolding c.MemWithoutConn() in c.closed) } +ghost +requires withConn ==> c.Mem() +requires !withConn ==> c.MemWithoutConn() +decreases +pure func (c *connUDPBase) GetRemote(withConn bool) *net.UDPAddr { + // TODO: very unsure how to format this + return withConn ? (unfolding c.Mem() in c.Remote) : (unfolding c.MemWithoutConn() in c.Remote) +} + +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires withConn ==> c.Mem() +requires !withConn ==> c.MemWithoutConn() +decreases +pure func (c *connUDPBase) IsLow(withConn bool) bool + +// "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +// TODO: could split IsLow and low(c.GetClosed) etc. on separate lines to avoid +// repeating them +// TODO: align wildcard probably (check if this is done elsewhere) +ghost +trusted +requires p > 0 +requires wildcard && withConn ==> acc(c.Mem(), _) && c.IsLow(true) +requires wildcard && !withConn ==> acc(c.MemWithoutConn(), _) && c.IsLow(false) +requires !wildcard && withConn ==> acc(c.Mem(), p) && c.IsLow(true) +requires !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) && c.IsLow(false) +ensures wildcard && withConn ==> acc(c.Mem(), _) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) +ensures wildcard && !withConn ==> acc(c.MemWithoutConn(), _) && low(c.GetClosed(false)) && low(c.GetRemote(false) != nil) +ensures !wildcard && withConn ==> acc(c.Mem(), p) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) +ensures !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) && low(c.GetClosed(false)) && low(c.GetRemote(false) != nil) +decreases +func (c *connUDPBase) RevealIsLow(wildcard bool, p perm, withConn bool) + +// Assert `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +// TODO: align wildcards, put lowness in separate ensures clauses +ghost +trusted +requires p > 0 +requires wildcard ==> acc(c.Mem(), _) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) +requires !wildcard ==> acc(c.Mem(), p) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) +ensures wildcard ==> acc(c.Mem(), _) && c.IsLow(true) +ensures !wildcard ==> acc(c.Mem(), p) && c.IsLow(true) +decreases +func (c *connUDPBase) AssertIsLow(wildcard bool, p perm) + // Shown to be satisfiable in newConnUDPIPv4 pred (c *connUDPIPv4) Mem() { acc(&c.pconn) && @@ -63,11 +106,26 @@ pred (c *connUDPIPv4) Mem() { c.pconn.GetUnderlyingConn() == (unfolding c.connUDPBase.MemWithoutConn() in c.conn) } -pred (c *connUDPIPv4) Low() { - acc(&c.pconn) && - c.pconn.Mem() && - c.connUDPBase.LowWithoutConn() -} +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires c.Mem() +decreases +pure func (c *connUDPIPv4) IsLow() bool + +// "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +// TODO: could consider using a "static" p in case this is only used in one place +// TODO: try to move actual lowness into separate ensures to avoid repitition +ghost +trusted +requires p > 0 +requires wildcard ==> acc(c.Mem(), _) && c.IsLow() +requires !wildcard ==> acc(c.Mem(), p) && c.IsLow() +ensures wildcard ==> acc(c.Mem(), _) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +ensures !wildcard ==> acc(c.Mem(), p) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +decreases +func (c *connUDPIPv4) RevealIsLow(wildcard bool, p perm) // Shown to be satisfiable in newConnUDPIPv6 pred (c *connUDPIPv6) Mem() { @@ -77,11 +135,26 @@ pred (c *connUDPIPv6) Mem() { c.pconn.GetUnderlyingConn() == (unfolding c.connUDPBase.MemWithoutConn() in c.conn) } -pred (c *connUDPIPv6) Low() { - acc(&c.pconn) && - c.pconn.Mem() && - c.connUDPBase.LowWithoutConn() -} +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires c.Mem() +decreases +pure func (c *connUDPIPv6) IsLow() bool + +// "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +// TODO: could consider using a "static" p in case this is only used in one place +// TODO: try to move actual lowness into separate ensures to avoid repitition +ghost +trusted +requires p > 0 +requires wildcard ==> acc(c.Mem(), _) && c.IsLow() +requires !wildcard ==> acc(c.Mem(), p) && c.IsLow() +ensures wildcard ==> acc(c.Mem(), _) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +ensures !wildcard ==> acc(c.Mem(), p) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +decreases +func (c *connUDPIPv6) RevealIsLow(wildcard bool, p perm) pred (c *Config) Mem() { acc(c) && @@ -89,10 +162,29 @@ pred (c *Config) Mem() { 0 <= c.ReceiveBufferSize } -pred (c *Config) Low() { - acc(c) && low(*c) +requires c.Mem() +decreases +pure func (c *Config) Dereference() Config { + return unfolding c.Mem() in *c } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires c.Mem() +decreases +pure func (c *Config) IsLow() bool + +// "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +ghost +trusted +requires p > 0 +requires acc(c.Mem(), p) && c.IsLow() +ensures acc(c.Mem(), p) && low(c.Dereference()) +decreases +func (c *Config) RevealIsLow(p perm) + /** Lift methods in *connUDPBase to *connUDPIPv4 **/ *connUDPIPv4 implements Conn @@ -131,22 +223,22 @@ func (c *connUDPIPv4) Write(b []byte) (n int, err error) { } requires acc(dst.Mem(), _) -preserves acc(c.Mem(), _) && acc(c.Low(), _) +preserves acc(c.Mem(), _) && c.IsLow() preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv4) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { + c.RevealIsLow(true, perm(1)) unfold acc(c.Mem(), _) + c.connUDPBase.RevealIsLow(true, perm(1), false) unfold acc(c.connUDPBase.MemWithoutConn(), _) - unfold acc(c.Low(), _) - unfold acc(c.connUDPBase.LowWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn tmpItf := c.pconn.ExchangeWildcardPerm() var packetconn *ipv4.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) - fold acc(c.connUDPBase.Low(), _) + c.connUDPBase.AssertIsLow(true, perm(1)) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -169,17 +261,17 @@ func (c *connUDPIPv4) RemoteAddr() (u *net.UDPAddr) { return c.connUDPBase.RemoteAddr() } -requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +requires c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv4) Close() (err error) { - unfold acc(c.Mem(), 1/2) - unfold acc(c.connUDPBase.MemWithoutConn(), 1/2) - unfold acc(c.Low(), 1/2) - unfold acc(c.connUDPBase.LowWithoutConn(), 1/2) + c.RevealIsLow(false, R1) + unfold c.Mem() + c.connUDPBase.RevealIsLow(false, R1, false) + unfold c.connUDPBase.MemWithoutConn() c.pconn.ExchangePerm() - fold acc(c.connUDPBase.Mem(), 1/2) - fold acc(c.connUDPBase.Low(), 1/2) + fold c.connUDPBase.Mem() + c.connUDPBase.AssertIsLow(false, R1) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv4 **/ @@ -223,22 +315,22 @@ func (c *connUDPIPv6) Write(b []byte) (n int, err error) { } requires acc(dst.Mem(), _) -preserves acc(c.Mem(), _) && acc(c.Low(), _) +preserves acc(c.Mem(), _) && c.IsLow() preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv6) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { + c.RevealIsLow(true, perm(1)) unfold acc(c.Mem(), _) + c.connUDPBase.RevealIsLow(true, perm(1), false) unfold acc(c.connUDPBase.MemWithoutConn(), _) - unfold acc(c.Low(), _) - unfold acc(c.connUDPBase.LowWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn tmpItf := c.pconn.ExchangeWildcardPerm() var packetconn *ipv6.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) - fold acc(c.connUDPBase.Low(), _) + c.connUDPBase.AssertIsLow(true, perm(1)) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -261,17 +353,17 @@ func (c *connUDPIPv6) RemoteAddr() (u *net.UDPAddr) { return c.connUDPBase.RemoteAddr() } -requires acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +requires c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv6) Close() (err error) { - unfold acc(c.Mem(), 1/2) - unfold acc(c.connUDPBase.MemWithoutConn(), 1/2) - unfold acc(c.Low(), 1/2) - unfold acc(c.connUDPBase.LowWithoutConn(), 1/2) + c.RevealIsLow(false, R1) + unfold c.Mem() + c.connUDPBase.RevealIsLow(false, R1, false) + unfold c.connUDPBase.MemWithoutConn() c.pconn.ExchangePerm() - fold acc(c.connUDPBase.Mem(), 1/2) - fold acc(c.connUDPBase.Low(), 1/2) + fold c.connUDPBase.Mem() + c.connUDPBase.AssertIsLow(false, R1) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv6 **/ diff --git a/private/underlay/sockctrl/sockopt.go b/private/underlay/sockctrl/sockopt.go index 6cb0ec0b5..b5db0b3f6 100644 --- a/private/underlay/sockctrl/sockopt.go +++ b/private/underlay/sockctrl/sockopt.go @@ -21,11 +21,12 @@ import ( "syscall" ) +// TODO[henri]: How do I justify this annotation? / justify this annotation! // @ trusted -// @ requires low(level) && low(opt) -// @ preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +// @ requires low(level) && low(opt) +// @ preserves c.Mem() && c.IsLow() // @ ensures e != nil ==> e.ErrorMem() -// @ ensures low(r) && low(e) +// @ ensures low(r) && low(e) // @ decreases _ func GetsockoptInt(c *net.UDPConn, level, opt int) (r int, e error) { var val int diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index d3fd354fd..f72a4d142 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -28,16 +28,41 @@ pred (a *UDPAddr) Mem() { acc(a, R5) && sl.Bytes(a.IP, 0, len(a.IP)) } -pred (a *UDPAddr) Low() { - acc(a, R5) && sl.Bytes(a.IP, 0, len(a.IP)) && - low(len(a.IP)) && - (forall i int :: { sl.GetByte(a.IP, 0, len(a.IP), i) } 0 <= i && i < len(a.IP) ==> - low(sl.GetByte(a.IP, 0, len(a.IP), i))) && - // Given that all elements of `a.IP` are low, this should be implied; - // however, without a body for `isZeros`, we state it here explicitly. - (len(a.IP) == IPv6len ==> low(isZeros(a.IP[0:10]))) +requires a.Mem() +decreases +pure func (a *UDPAddr) GetIP() IP { + return unfolding a.Mem() in a.IP +} + +requires a.Mem() && 0 <= i && i < len(a.GetIP()) +decreases +pure func (a *UDPAddr) GetIPByte(i int) byte { + return unfolding a.Mem() in sl.GetByte(a.IP, 0, len(a.IP), i) } +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires a.Mem() +decreases +pure func (a *UDPAddr) IsLow() bool + +// "Reveal the body" of `a.IsLow` (necessary bc. we can't implement `a.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +ghost +trusted +requires p > 0 +requires acc(a.Mem(), p) && a.IsLow() +ensures acc(a.Mem(), p) +ensures low(len(a.GetIP())) +ensures forall i int :: { a.GetIPByte(i) } 0 <= i && i < len(a.GetIP()) ==> + low(a.GetIPByte(i)) +// Given that all elements of `a.IP` are low, this should be implied; however, +// without a body for `isZeros`, we state it here explicitly. +ensures len(a.GetIP()) == IPv6len ==> low(isZeros(a.GetIP()[0:10])) +decreases +func (a *UDPAddr) RevealIsLow(p perm) + (*UDPAddr) implements Addr { (e *UDPAddr) Network() string { return e.Network() @@ -67,9 +92,17 @@ pred (u *UDPConn) Mem() { acc(u) } -pred (u *UDPConn) Low() { - acc(u) && low(*u) -} +// TODO: remove +// pred (u *UDPConn) Low() { +// acc(u) && low(*u) +// } + +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +ghost +trusted +requires c.Mem() +decreases +pure func (c *UDPConn) IsLow() bool // ReadFromUDP acts like ReadFrom but returns a UDPAddr. preserves acc(c.Mem(), _) @@ -127,33 +160,33 @@ ensures err != nil ==> err.ErrorMem() decreases func (c *UDPConn) Close() (err error) -requires acc(laddr.Mem(), _) && acc(laddr.Low(), _) -requires low(network) -ensures err == nil ==> acc(conn.Mem(), 1/2) && acc(conn.Low(), 1/2) +requires acc(laddr.Mem(), _) +requires laddr.IsLow() && low(network) +ensures err == nil ==> conn.Mem() && conn.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err) decreases _ func ListenUDP(network string, laddr *UDPAddr) (conn *UDPConn, err error) -requires low(network) -requires acc(laddr.Mem(), _) && acc(laddr.Low(), _) -requires acc(raddr.Mem(), _) && acc(raddr.Low(), _) -ensures err == nil ==> acc(conn.Mem(), 1/2) && acc(conn.Low(), 1/2) +requires acc(laddr.Mem(), _) +requires acc(raddr.Mem(), _) +requires low(network) && laddr.IsLow() && raddr.IsLow() +ensures err == nil ==> conn.Mem() && conn.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err) decreases _ func DialUDP(network string, laddr, raddr *UDPAddr) (conn *UDPConn, err error) -requires low(bytes) -preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) +requires low(bytes) +preserves c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err) decreases _ func (c *UDPConn) SetWriteBuffer(bytes int) (err error) -requires low(bytes) -preserves acc(c.Mem(), 1/2) && acc(c.Low(), 1/2) -ensures low(err) +requires low(bytes) +preserves c.Mem() && c.IsLow() +ensures low(err) ensures err != nil ==> err.ErrorMem() decreases _ func (c *UDPConn) SetReadBuffer(bytes int) (err error) From 9acac019dd264276439a96685b7dd03e4c78e883 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 13:16:48 +0200 Subject: [PATCH 056/104] Adjust annotations of serrors Previously we required all elements of `errCtx` to be low. Aside from the fact that this annotation didn't make sense in case `errCtx` contained values of reference types, it was also unnecessary, since (by now) we only require that whether the resulting error is not nil to be low -- which was already guaranteed by the contract. We do, however, need to ensure that there is no non-low control flow. This might require further annotations in the future. --- pkg/private/serrors/serrors_spec.gobra | 16 +++++++--------- private/topology/underlay/defs.go | 6 ------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 79f15f64d..1e4e0ab99 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -90,17 +90,15 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) -// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` -// using a pure Boolean function. -requires low(msg) && low(len(errCtx)) && - forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) -ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) -ensures res != nil && res.ErrorMem() -ensures res.IsDuplicableMem() +// TODO: For completeness, we need to require every value that influences +// control flow (in the implementation of `New`) to be low. This annotation is +// likely not sufficient. +requires low(len(errCtx)) +preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +ensures res != nil && res.ErrorMem() +ensures res.IsDuplicableMem() // New always returns a pointer to a basicError, thus it // only produces comparable values ensures isComparable(res) -ensures low(res != nil) decreases func New(msg string, errCtx ...interface{}) (res error) diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index f7badb21d..286f953eb 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -73,12 +73,6 @@ func TypeFromString(s string) (t Type, err error) { case strings.ToLower(UDPIPv46Name): return UDPIPv46, nil default: - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - // @ ghost errCtx := []interface{}{"type", s} - // @ assert len(errCtx) == 2 - // @ assert low(errCtx[0]) - // @ assert low(errCtx[1]) - // @ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return Invalid, serrors.New("Unknown underlay type", "type", s) } } From 8f3c41c7e381bb8b844c41f8fff1359e58c461fa Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 13:25:12 +0200 Subject: [PATCH 057/104] Remove workaround for Gobra issue #835/#890 Due to the change in the contract of serrors, the workaround is not necessary anymore --- private/topology/linktype.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 8c32a1a1e..836d0df45 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -143,9 +143,6 @@ func (l *LinkType) UnmarshalText(data []byte) (err error) { case "peer": *l = Peer default: - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ ghost errCtx := []interface{}{"linkType", string(data)} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return serrors.New("invalid link type", "linkType", string(data)) } return nil From d6d212f1c956385aa05229360269072d1e7c82e8 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 13:46:18 +0200 Subject: [PATCH 058/104] Adjust annotations for serrors This also allowed the removal of several workarounds for bugs 835/890 --- pkg/addr/isdas.go | 24 ------------------------ pkg/private/serrors/serrors_spec.gobra | 12 +++++------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index f6433a398..9825be1f8 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -94,11 +94,6 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { } if len(parts) != asParts { - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(sep) - //@ assert low(_as) - //@ ghost errCtx := []interface{}{"sep", sep, "value", _as} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("wrong number of separators", "sep", sep, "value", _as) } var parsed AS @@ -111,11 +106,6 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { parsed <<= asPartBits v, err := strconv.ParseUint(parts[i], asPartBase, asPartBits) if err != nil { - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(i) - //@ assert low(_as) - //@ ghost errCtx := []interface{}{"index", i, "value", _as} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.WrapStr("parsing AS part", err, "index", i, "value", _as) } parsed |= AS(v) @@ -124,11 +114,6 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { // against future refactor mistakes. if !parsed.inRange() { // (VerifiedSCION) Added cast around MaxAS to be able to call serrors.New - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(uint64(MaxAS)) - //@ assert low(_as) - //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", _as} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("AS out of range", "max", uint64(MaxAS), "value", _as) } return parsed, nil @@ -175,11 +160,6 @@ func (_as AS) inRange() bool { func (_as AS) MarshalText() ([]byte, error) { if !_as.inRange() { // (VerifiedSCION) Added cast around MaxAS and as to be able to call serrors.New - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(uint64(MaxAS)) - //@ assert low(uint64(_as)) - //@ ghost errCtx := []interface{}{"max", uint64(MaxAS), "value", uint64(_as)} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return nil, serrors.New("AS out of range", "max", uint64(MaxAS), "value", uint64(_as)) } return []byte(_as.String()), nil @@ -244,10 +224,6 @@ func IAFrom(isd ISD, _as AS) (ia IA, err error) { func ParseIA(ia string) (retIA IA, retErr error) { parts := strings.Split(ia, "-") if len(parts) != 2 { - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(ia) - //@ ghost errCtx := []interface{}{"value", ia} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) return 0, serrors.New("invalid ISD-AS", "value", ia) } isd, err := ParseISD(parts[0]) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 7791e800b..dda8410ad 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,18 +76,16 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -requires forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +// TODO: For completeness, we need to require every value that influences +// control flow (in the implementation of `WrapStr`) to be low. This annotation +// is not sufficient. +requires low(len(errCtx)) requires cause != nil ==> cause.ErrorMem() -// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `errCtx` -// using a pure Boolean function. -requires low(msg) && low(cause) && low(len(errCtx)) && - forall i int :: { errCtx[i] } 0 <= i && i < len(errCtx) ==> low(errCtx[i]) -ensures forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) +preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. // requires forall i int :: 0 <= i && i < len(errCtx) ==> IsOfPrimitiveType(errCtx[i]) ensures res != nil && res.ErrorMem() ensures cause != nil ==> (res.ErrorMem() --* cause.ErrorMem()) -ensures low(res != nil) decreases func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) From a8b3c64bd474ef94cbd1b5c2475e7bf59dcec6d0 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 15:17:36 +0200 Subject: [PATCH 059/104] Remove unnecessary annotations --- pkg/addr/fmt.go | 5 ----- pkg/addr/host.go | 6 ------ 2 files changed, 11 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index 35a2f413f..0ca5657ee 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -117,11 +117,6 @@ func FormatAS(as_ AS, opts ...FormatOption) string { // @ decreases func fmtAS(as_ AS, sep string) (res string) { if !as_.inRange() { - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - // @ assert low(as_) - // @ assert low(MaxAS) - // @ ghost v := []interface{}{as_, MaxAS} - // @ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%d [Illegal AS: larger than %d]", as_, MaxAS) } // Format BGP ASes as decimal diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 55db5eb1d..6d40bdb54 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -429,12 +429,6 @@ func (h HostSVC) String() string { if h.IsMulticast() { cast = 'M' } - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ assert low(name) - //@ assert low(cast) - //@ assert low(uint16(h)) - //@ ghost v := []interface{}{name, cast, uint16(h)} - //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%v %c (0x%04x)", name, cast, uint16(h)) } From 09da9b377d5cfa5d9822216c3896398995fb3f1a Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 16:15:05 +0200 Subject: [PATCH 060/104] Fix sensitivity annotations of pointers --- private/underlay/conn/conn.go | 126 ++------------------ verification/dependencies/net/udpsock.gobra | 8 +- 2 files changed, 17 insertions(+), 117 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 30cd46965..c0d20a698 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -44,7 +44,6 @@ type Messages []ipv4.Message // Conn describes the API for an underlay socket type Conn interface { //@ pred Mem() - // pred Low() //@ ghost //@ requires Mem() //@ decreases @@ -120,7 +119,7 @@ type Config struct { // @ requires listen != nil || remote != nil // @ requires listen != nil ==> acc(listen.Mem(), R10) && listen.IsLow() // @ requires remote != nil ==> acc(remote.Mem(), R10) && remote.IsLow() -// @ requires low(listen) && low(remote) && cfg.IsLow() +// @ requires low(listen != nil) && low(remote != nil) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -153,7 +152,7 @@ type connUDPIPv4 struct { // @ requires cfg.Mem() // @ requires listen != nil ==> acc(listen.Mem(), _) && listen.IsLow() // @ requires remote != nil ==> acc(remote.Mem(), _) && remote.IsLow() -// @ requires low(listen) && low(remote) && cfg.IsLow() +// @ requires low(listen != nil) && low(remote != nil) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -228,7 +227,7 @@ type connUDPIPv6 struct { // @ requires cfg.Mem() // @ requires listen != nil ==> acc(listen.Mem(), _) && listen.IsLow() // @ requires remote != nil ==> acc(remote.Mem(), _) && remote.IsLow() -// @ requires low(listen) && low(remote) && cfg.IsLow() +// @ requires low(listen != nil) && low(remote != nil) && cfg.IsLow() // @ ensures e == nil ==> res.Mem() // @ ensures e != nil ==> e.ErrorMem() // @ decreases @@ -306,7 +305,7 @@ type connUDPBase struct { // @ requires laddr != nil ==> acc(laddr.Mem(), _) && laddr.IsLow() // @ requires raddr != nil ==> acc(raddr.Mem(), _) && raddr.IsLow() // @ requires cfg.Mem() -// @ requires low(network) && low(laddr) && low(raddr) && cfg.IsLow() +// @ requires low(network) && low(laddr != nil) && low(raddr != nil) && cfg.IsLow() // @ ensures errRet == nil ==> cc.Mem() // @ ensures errRet != nil ==> errRet.ErrorMem() // @ ensures low(errRet != nil) @@ -315,44 +314,17 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf var c *net.UDPConn var err error if laddr == nil { - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.New("listen address must be specified") - // @ assert low(reterr != nil) - return reterr - // return serrors.New("listen address must be specified") + return serrors.New("listen address must be specified") } if raddr == nil { if c, err = net.ListenUDP(network, laddr); err != nil { - // @ assert low(err) - // @ assert low(network) - // @ assert low(laddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"network", network, "listen", laddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error listening on socket", err, + return serrors.WrapStr("Error listening on socket", err, "network", network, "listen", laddr) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error listening on socket", err, - // "network", network, "listen", laddr) } } else { if c, err = net.DialUDP(network, laddr, raddr); err != nil { - // @ assert low(err) - // @ assert low(network) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"network", network, "listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error setting up connection", err, + return serrors.WrapStr("Error setting up connection", err, "network", network, "listen", laddr, "remote", raddr) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error setting up connection", err, - // "network", network, "listen", laddr, "remote", raddr) } } @@ -362,60 +334,24 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf if cfg.SendBufferSize != 0 { beforeV, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_SNDBUF) if err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, + return serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error getting SO_SNDBUF socket option (before)", err, - // "listen", laddr, - // "remote", raddr, - // ) } target := cfg.SendBufferSize if err = c.SetWriteBuffer(target); err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error setting send buffer size", err, + return serrors.WrapStr("Error setting send buffer size", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error setting send buffer size", err, - // "listen", laddr, - // "remote", raddr, - // ) } after, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_SNDBUF) if err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, + return serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error getting SO_SNDBUF socket option (after)", err, - // "listen", laddr, - // "remote", raddr, - // ) } if after/2 < target { // Note: kernel doubles value passed in SetSendBuffer, value @@ -432,60 +368,24 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf { beforeV, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_RCVBUF) if err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, + return serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error getting SO_RCVBUF socket option (before)", err, - // "listen", laddr, - // "remote", raddr, - // ) } target := cfg.ReceiveBufferSize if err = c.SetReadBuffer(target); err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error setting recv buffer size", err, + return serrors.WrapStr("Error setting recv buffer size", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error setting recv buffer size", err, - // "listen", laddr, - // "remote", raddr, - // ) } after, err := sockctrl.GetsockoptInt(c, syscall.SOL_SOCKET, syscall.SO_RCVBUF) if err != nil { - // @ assert low(err) - // @ assert low(laddr) - // @ assert low(raddr) - // SIF: See Gobra issue #835 for why this assumption is currently necessary - //@ ghost errCtx := []interface{}{"listen", laddr, "remote", raddr} - //@ assume forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i]) && low(errCtx[i]) - reterr := serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, + return serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, "listen", laddr, "remote", raddr, ) - // @ assert low(reterr != nil) - return reterr - // return serrors.WrapStr("Error getting SO_RCVBUF socket option (after)", err, - // "listen", laddr, - // "remote", raddr, - // ) } if after/2 < target { // Note: kernel doubles value passed in SetReadBuffer, value diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index f72a4d142..70eb50ceb 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -164,7 +164,7 @@ requires acc(laddr.Mem(), _) requires laddr.IsLow() && low(network) ensures err == nil ==> conn.Mem() && conn.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err != nil) decreases _ func ListenUDP(network string, laddr *UDPAddr) (conn *UDPConn, err error) @@ -173,20 +173,20 @@ requires acc(raddr.Mem(), _) requires low(network) && laddr.IsLow() && raddr.IsLow() ensures err == nil ==> conn.Mem() && conn.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err != nil) decreases _ func DialUDP(network string, laddr, raddr *UDPAddr) (conn *UDPConn, err error) requires low(bytes) preserves c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() -ensures low(err) +ensures low(err != nil) decreases _ func (c *UDPConn) SetWriteBuffer(bytes int) (err error) requires low(bytes) preserves c.Mem() && c.IsLow() -ensures low(err) +ensures low(err != nil) ensures err != nil ==> err.ErrorMem() decreases _ func (c *UDPConn) SetReadBuffer(bytes int) (err error) From 43b4ab3e86569dd5cc35d23e0f6b58b00a98718b Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 2 Jul 2025 17:36:20 +0200 Subject: [PATCH 061/104] Clean up --- private/underlay/conn/conn.go | 12 +- private/underlay/conn/conn_spec.gobra | 106 +++++++++--------- verification/dependencies/net/udpsock.gobra | 16 +-- .../utils/definitions/definitions.gobra | 4 + 4 files changed, 66 insertions(+), 72 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index c0d20a698..6f2375cf5 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -133,7 +133,7 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { } // @ assert remote != nil ==> a == remote // @ assert remote == nil ==> a == listen - // @ a.RevealIsLow(R15) + // @ a.RevealIsLow() // @ assert len(a.GetIP()) == net.IPv6len ==> // @ low(a.GetIPByte(10) == 255) && low(a.GetIPByte(11) == 255) // @ unfold acc(a.Mem(), R15) @@ -328,7 +328,7 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf } } - //@ cfg.RevealIsLow(perm(1)) + //@ cfg.RevealIsLow() //@ unfold cfg.Mem() // Set and confirm send buffer size if cfg.SendBufferSize != 0 { @@ -433,7 +433,7 @@ func (c *connUDPBase) Write(b []byte /*@, ghost underlyingConn *net.UDPConn @*/) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() func (c *connUDPBase) WriteTo(b []byte, dst *net.UDPAddr /*@, ghost underlyingConn *net.UDPConn @*/) (n int, err error) { - //@ c.RevealIsLow(true, perm(1), true) + //@ c.RevealIsLow(true, anyPerm, true) //@ unfold acc(c.Mem(), _) if c.Remote != nil { return c.conn.Write(b) @@ -463,7 +463,7 @@ func (c *connUDPBase) RemoteAddr() (u *net.UDPAddr) { // @ ensures err != nil ==> err.ErrorMem() // @ decreases func (c *connUDPBase) Close() (err error) { - //@ c.RevealIsLow(false, perm(1), true) + //@ c.RevealIsLow(false, writePerm, true) //@ unfold c.Mem() if c.closed { return nil @@ -472,9 +472,9 @@ func (c *connUDPBase) Close() (err error) { return c.conn.Close() } -// NOTE[henri]: the verification of this function seems to be very unstable // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. +// NOTE: The verification of this function is unstable. // @ requires 0 < n // @ requires low(n) // @ ensures len(res) == n @@ -482,11 +482,11 @@ func (c *connUDPBase) Close() (err error) { // @ decreases func NewReadMessages(n int) (res Messages) { m := make(Messages, n) - //@ invariant low(i0) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() && m[j].GetAddr() == nil //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) && m[j].N == 0 //@ invariant forall j int :: { m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil //@ invariant forall j int :: { m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil + //@ invariant low(i0) //@ decreases len(m) - i for i := range m /*@ with i0 @*/ { // Allocate a single-element, to avoid allocations when setting the buffer. diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index f7f810cfa..da2ad65e2 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -41,62 +41,61 @@ pred (c *connUDPBase) MemWithoutConn() { } ghost -requires withConn ==> c.Mem() +requires withConn ==> c.Mem() requires !withConn ==> c.MemWithoutConn() decreases pure func (c *connUDPBase) GetClosed(withConn bool) bool { - // TODO: very unsure how to format this - return withConn ? (unfolding c.Mem() in c.closed) : (unfolding c.MemWithoutConn() in c.closed) + return withConn ? ( + unfolding c.Mem() in c.closed) : (unfolding c.MemWithoutConn() in c.closed) } ghost -requires withConn ==> c.Mem() +requires withConn ==> c.Mem() requires !withConn ==> c.MemWithoutConn() decreases pure func (c *connUDPBase) GetRemote(withConn bool) *net.UDPAddr { - // TODO: very unsure how to format this - return withConn ? (unfolding c.Mem() in c.Remote) : (unfolding c.MemWithoutConn() in c.Remote) + return withConn ? ( + unfolding c.Mem() in c.Remote) : (unfolding c.MemWithoutConn() in c.Remote) } // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted -requires withConn ==> c.Mem() +requires withConn ==> c.Mem() requires !withConn ==> c.MemWithoutConn() decreases pure func (c *connUDPBase) IsLow(withConn bool) bool // "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). // TODO: Once Gobra issue #846 is resolved, implement this. -// TODO: could split IsLow and low(c.GetClosed) etc. on separate lines to avoid -// repeating them -// TODO: align wildcard probably (check if this is done elsewhere) ghost trusted requires p > 0 -requires wildcard && withConn ==> acc(c.Mem(), _) && c.IsLow(true) -requires wildcard && !withConn ==> acc(c.MemWithoutConn(), _) && c.IsLow(false) -requires !wildcard && withConn ==> acc(c.Mem(), p) && c.IsLow(true) -requires !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) && c.IsLow(false) -ensures wildcard && withConn ==> acc(c.Mem(), _) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) -ensures wildcard && !withConn ==> acc(c.MemWithoutConn(), _) && low(c.GetClosed(false)) && low(c.GetRemote(false) != nil) -ensures !wildcard && withConn ==> acc(c.Mem(), p) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) -ensures !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) && low(c.GetClosed(false)) && low(c.GetRemote(false) != nil) +requires wildcard && withConn ==> acc(c.Mem(), _) +requires wildcard && !withConn ==> acc(c.MemWithoutConn(), _) +requires !wildcard && withConn ==> acc(c.Mem(), p) +requires !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) +requires c.IsLow(withConn) +ensures wildcard && withConn ==> acc(c.Mem(), _) +ensures wildcard && !withConn ==> acc(c.MemWithoutConn(), _) +ensures !wildcard && withConn ==> acc(c.Mem(), p) +ensures !wildcard && !withConn ==> acc(c.MemWithoutConn(), p) +ensures low(c.GetClosed(withConn)) && low(c.GetRemote(withConn) != nil) decreases func (c *connUDPBase) RevealIsLow(wildcard bool, p perm, withConn bool) // Assert `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). // TODO: Once Gobra issue #846 is resolved, implement this. -// TODO: align wildcards, put lowness in separate ensures clauses ghost trusted -requires p > 0 -requires wildcard ==> acc(c.Mem(), _) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) -requires !wildcard ==> acc(c.Mem(), p) && low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) -ensures wildcard ==> acc(c.Mem(), _) && c.IsLow(true) -ensures !wildcard ==> acc(c.Mem(), p) && c.IsLow(true) +requires wildcard ==> acc(c.Mem(), _) +requires !wildcard ==> acc(c.Mem(), R1) +requires low(c.GetClosed(true)) && low(c.GetRemote(true) != nil) +ensures wildcard ==> acc(c.Mem(), _) +ensures !wildcard ==> acc(c.Mem(), R1) +ensures c.IsLow(true) decreases -func (c *connUDPBase) AssertIsLow(wildcard bool, p perm) +func (c *connUDPBase) AssertIsLow(wildcard bool) // Shown to be satisfiable in newConnUDPIPv4 pred (c *connUDPIPv4) Mem() { @@ -115,17 +114,16 @@ pure func (c *connUDPIPv4) IsLow() bool // "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). // TODO: Once Gobra issue #846 is resolved, implement this. -// TODO: could consider using a "static" p in case this is only used in one place -// TODO: try to move actual lowness into separate ensures to avoid repitition ghost trusted -requires p > 0 -requires wildcard ==> acc(c.Mem(), _) && c.IsLow() -requires !wildcard ==> acc(c.Mem(), p) && c.IsLow() -ensures wildcard ==> acc(c.Mem(), _) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) -ensures !wildcard ==> acc(c.Mem(), p) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +requires wildcard ==> acc(c.Mem(), _) +requires !wildcard ==> acc(c.Mem(), R1) +requires c.IsLow() +ensures wildcard ==> acc(c.Mem(), _) +ensures !wildcard ==> acc(c.Mem(), R1) +ensures unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) decreases -func (c *connUDPIPv4) RevealIsLow(wildcard bool, p perm) +func (c *connUDPIPv4) RevealIsLow(wildcard bool) // Shown to be satisfiable in newConnUDPIPv6 pred (c *connUDPIPv6) Mem() { @@ -144,17 +142,16 @@ pure func (c *connUDPIPv6) IsLow() bool // "Reveal the body" of `c.IsLow` (necessary bc. we can't implement `c.IsLow` yet). // TODO: Once Gobra issue #846 is resolved, implement this. -// TODO: could consider using a "static" p in case this is only used in one place -// TODO: try to move actual lowness into separate ensures to avoid repitition ghost trusted -requires p > 0 -requires wildcard ==> acc(c.Mem(), _) && c.IsLow() -requires !wildcard ==> acc(c.Mem(), p) && c.IsLow() -ensures wildcard ==> acc(c.Mem(), _) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) -ensures !wildcard ==> acc(c.Mem(), p) && unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) +requires wildcard ==> acc(c.Mem(), _) +requires !wildcard ==> acc(c.Mem(), R1) +requires c.IsLow() +ensures wildcard ==> acc(c.Mem(), _) +ensures !wildcard ==> acc(c.Mem(), R1) +ensures unfolding acc(c.Mem(), _) in c.connUDPBase.IsLow(false) decreases -func (c *connUDPIPv6) RevealIsLow(wildcard bool, p perm) +func (c *connUDPIPv6) RevealIsLow(wildcard bool) pred (c *Config) Mem() { acc(c) && @@ -179,11 +176,10 @@ pure func (c *Config) IsLow() bool // TODO: Once Gobra issue #846 is resolved, implement this. ghost trusted -requires p > 0 -requires acc(c.Mem(), p) && c.IsLow() -ensures acc(c.Mem(), p) && low(c.Dereference()) +requires acc(c.Mem(), R1) && c.IsLow() +ensures acc(c.Mem(), R1) && low(c.Dereference()) decreases -func (c *Config) RevealIsLow(p perm) +func (c *Config) RevealIsLow() /** Lift methods in *connUDPBase to *connUDPIPv4 **/ *connUDPIPv4 implements Conn @@ -228,9 +224,9 @@ preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv4) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { - c.RevealIsLow(true, perm(1)) + c.RevealIsLow(true) unfold acc(c.Mem(), _) - c.connUDPBase.RevealIsLow(true, perm(1), false) + c.connUDPBase.RevealIsLow(true, anyPerm, false) unfold acc(c.connUDPBase.MemWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn @@ -238,7 +234,7 @@ func (c *connUDPIPv4) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { var packetconn *ipv4.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) - c.connUDPBase.AssertIsLow(true, perm(1)) + c.connUDPBase.AssertIsLow(true) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -265,13 +261,13 @@ requires c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv4) Close() (err error) { - c.RevealIsLow(false, R1) + c.RevealIsLow(false) unfold c.Mem() c.connUDPBase.RevealIsLow(false, R1, false) unfold c.connUDPBase.MemWithoutConn() c.pconn.ExchangePerm() fold c.connUDPBase.Mem() - c.connUDPBase.AssertIsLow(false, R1) + c.connUDPBase.AssertIsLow(false) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv4 **/ @@ -320,9 +316,9 @@ preserves acc(sl.Bytes(b, 0, len(b)), R15) ensures err == nil ==> 0 <= n && n <= len(b) ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv6) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { - c.RevealIsLow(true, perm(1)) + c.RevealIsLow(true) unfold acc(c.Mem(), _) - c.connUDPBase.RevealIsLow(true, perm(1), false) + c.connUDPBase.RevealIsLow(true, anyPerm, false) unfold acc(c.connUDPBase.MemWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn @@ -330,7 +326,7 @@ func (c *connUDPIPv6) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { var packetconn *ipv6.PacketConn = c.pconn assert tmpItf == c.conn fold acc(c.connUDPBase.Mem(), _) - c.connUDPBase.AssertIsLow(true, perm(1)) + c.connUDPBase.AssertIsLow(true) n1, err1 := c.connUDPBase.WriteTo(b, dst, tmpImpl) return n1, err1 } @@ -357,13 +353,13 @@ requires c.Mem() && c.IsLow() ensures err != nil ==> err.ErrorMem() decreases func (c *connUDPIPv6) Close() (err error) { - c.RevealIsLow(false, R1) + c.RevealIsLow(false) unfold c.Mem() c.connUDPBase.RevealIsLow(false, R1, false) unfold c.connUDPBase.MemWithoutConn() c.pconn.ExchangePerm() fold c.connUDPBase.Mem() - c.connUDPBase.AssertIsLow(false, R1) + c.connUDPBase.AssertIsLow(false) c.connUDPBase.Close() } /** End of Lift methods in *connUDPBase to *connUDPIPv6 **/ diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index 70eb50ceb..b0ba3c169 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -51,17 +51,16 @@ pure func (a *UDPAddr) IsLow() bool // TODO: Once Gobra issue #846 is resolved, implement this. ghost trusted -requires p > 0 -requires acc(a.Mem(), p) && a.IsLow() -ensures acc(a.Mem(), p) +requires acc(a.Mem(), R15) && a.IsLow() +ensures acc(a.Mem(), R15) ensures low(len(a.GetIP())) ensures forall i int :: { a.GetIPByte(i) } 0 <= i && i < len(a.GetIP()) ==> low(a.GetIPByte(i)) -// Given that all elements of `a.IP` are low, this should be implied; however, -// without a body for `isZeros`, we state it here explicitly. +// Given that all elements of `a.IP` are low, this should be implied; +// however, without a body for `isZeros`, we state it here explicitly. ensures len(a.GetIP()) == IPv6len ==> low(isZeros(a.GetIP()[0:10])) decreases -func (a *UDPAddr) RevealIsLow(p perm) +func (a *UDPAddr) RevealIsLow() (*UDPAddr) implements Addr { (e *UDPAddr) Network() string { @@ -92,11 +91,6 @@ pred (u *UDPConn) Mem() { acc(u) } -// TODO: remove -// pred (u *UDPConn) Low() { -// acc(u) && low(*u) -// } - // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted diff --git a/verification/utils/definitions/definitions.gobra b/verification/utils/definitions/definitions.gobra index 3b458ff59..ca024f9e8 100644 --- a/verification/utils/definitions/definitions.gobra +++ b/verification/utils/definitions/definitions.gobra @@ -76,6 +76,10 @@ const ( R54 R55 R56 + // Some functions have parameters `wildcard bool` and `p perm`, and consume + // a wildcard permission amount if `wildcard` is set, and amount `p` + // otherwise. If `wildcard` is set, `p` should be set to `anyPerm`. + anyPerm ) // To be used as a precondition of functions and methods that can never be called From 21aa6a945a0d288798f3a98705e1ab1babac0276 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 3 Jul 2025 12:51:50 +0200 Subject: [PATCH 062/104] Add some clarifications --- private/underlay/sockctrl/sockopt.go | 3 ++- verification/dependencies/net/udpsock.gobra | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/private/underlay/sockctrl/sockopt.go b/private/underlay/sockctrl/sockopt.go index b5db0b3f6..fb2013ec7 100644 --- a/private/underlay/sockctrl/sockopt.go +++ b/private/underlay/sockctrl/sockopt.go @@ -21,7 +21,8 @@ import ( "syscall" ) -// TODO[henri]: How do I justify this annotation? / justify this annotation! +// As the implementation of `GetsockoptInt` is a bit involved, we (possibly) +// overspecify this function to recover low(inputs) ==> low(outputs). // @ trusted // @ requires low(level) && low(opt) // @ preserves c.Mem() && c.IsLow() diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index b0ba3c169..63dd783eb 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -41,6 +41,9 @@ pure func (a *UDPAddr) GetIPByte(i int) byte { } // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +// - Here, `IsLow` will need to cover all fields (of `UDPAddr`), since we use +// this information in `DialUDP` and `ListenUDP` to imply that all fields of +// the resulting `UDPConn` object are low. ghost trusted requires a.Mem() @@ -92,6 +95,8 @@ pred (u *UDPConn) Mem() { } // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +// - Here, `IsLow` will need to cover all fields (of `UDPConn`), since we use +// this information in private/underlay/sockctrl/sockopt.go, GetsockoptInt. ghost trusted requires c.Mem() @@ -155,7 +160,7 @@ decreases func (c *UDPConn) Close() (err error) requires acc(laddr.Mem(), _) -requires laddr.IsLow() && low(network) +requires low(network) && laddr.IsLow() ensures err == nil ==> conn.Mem() && conn.IsLow() ensures err != nil ==> err.ErrorMem() ensures low(err != nil) From 182a483f214ced02431ec11d48bec5dced9dbb83 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 3 Jul 2025 17:14:56 +0200 Subject: [PATCH 063/104] Clean up --- pkg/slayers/path/empty/empty.go | 1 - pkg/slayers/path/path.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/slayers/path/empty/empty.go b/pkg/slayers/path/empty/empty.go index 50885661f..36f2705dd 100644 --- a/pkg/slayers/path/empty/empty.go +++ b/pkg/slayers/path/empty/empty.go @@ -26,7 +26,6 @@ const PathLen = 0 const PathType path.Type = 0 -// TODO: Once Gobra issue 878 is resolved, remove `trusted`. // @ requires path.PkgMem() // @ requires path.RegisteredTypes().DoesNotContain(int64(PathType)) // @ ensures path.PkgMem() diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 14832846f..bd3b2ddf5 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -85,7 +85,7 @@ type Path interface { // (VerifiedSCION) There are implementations of this interface (e.g., scion.Raw) that // store b and use it as internal data. //@ requires NonInitMem() - //@ requires low(len(b)) + //@ requires low(len(b)) //@ preserves acc(sl.Bytes(b, 0, len(b)), R42) //@ ensures err == nil ==> Mem(b) //@ ensures err == nil ==> IsValidResultOfDecoding(b) From 23a050fe36a242e9857060dbcda6a7556ff98d3a Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 3 Jul 2025 18:02:00 +0200 Subject: [PATCH 064/104] Clean up --- pkg/slayers/path/hopfield.go | 2 +- pkg/slayers/path/hopfield_spec.gobra | 6 +++++- pkg/slayers/path/mac.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index d08bc82c6..a5720cfd6 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -111,7 +111,7 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { // path.HopLen. // @ requires len(b) >= HopLen // @ requires acc(h.Mem(), R10) -// @ requires low(h.GetEgressRouterAlert()) && low(h.GetIngressRouterAlert()) +// @ requires low(h.GetEgressRouterAlert()) && low(h.GetIngressRouterAlert()) // @ preserves sl.Bytes(b, 0, HopLen) // @ ensures acc(h.Mem(), R10) // @ ensures err == nil diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index d0a81237b..e4de2bf76 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -115,8 +115,12 @@ pure func (h HopField) Abs() (io.HF) { HVF: AbsMac(h.Mac), } } -// TODO: Once Gobra issue 883 is removed, these could be removed in favor of +// TODO: Once Gobra issue 883 is resolved, these could be removed in favor of // unfolding permissions directly in the contract. +// - We might prefer such getter functions anyway, though. +// - Alternatively, we could also have a "`Dereference`" function returning +// the underlying `HopField` which we could then access. +// This way, we would only need to introduce 1 additional function here. ghost requires h.Mem() decreases diff --git a/pkg/slayers/path/mac.go b/pkg/slayers/path/mac.go index b48286598..bce6da97b 100644 --- a/pkg/slayers/path/mac.go +++ b/pkg/slayers/path/mac.go @@ -30,7 +30,7 @@ const MACBufferSize = 16 // this method does not modify info or hf. // Modifying the provided buffer after calling this function may change the returned HopField MAC. // @ requires h != nil && h.Mem() -// @ requires low(len(buffer) < MACBufferSize) +// @ requires low(len(buffer) < MACBufferSize) // @ preserves len(buffer) >= MACBufferSize ==> sl.Bytes(buffer, 0, len(buffer)) // @ ensures h.Mem() // @ decreases From 1d9a1d7ecc299810cd743253a65c3ca575d34b5d Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 5 Jul 2025 15:00:46 +0200 Subject: [PATCH 065/104] Rework sensitivity in interfaces --- pkg/slayers/path/epic/epic.go | 10 +++++----- pkg/slayers/path/epic/epic_spec.gobra | 23 ++++++++++++++++++----- pkg/slayers/path/path.go | 16 +++++++++++++--- pkg/slayers/path/path_spec.gobra | 7 ++++++- pkg/slayers/path/scion/decoded_spec.gobra | 7 ++++++- pkg/slayers/path/scion/raw_spec.gobra | 7 ++++++- 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/pkg/slayers/path/epic/epic.go b/pkg/slayers/path/epic/epic.go index 33decd271..1d165d33f 100644 --- a/pkg/slayers/path/epic/epic.go +++ b/pkg/slayers/path/epic/epic.go @@ -41,8 +41,6 @@ const ( ) // RegisterPath registers the EPIC path type globally. -// TODO: Once Gobra issue 878 is resolved, remove `trusted`. -// @ trusted // @ requires path.PkgMem() // @ requires path.RegisteredTypes().DoesNotContain(int64(PathType)) // @ ensures path.PkgMem() @@ -80,11 +78,12 @@ type Path struct { // SerializeTo serializes the Path into buffer b. On failure, an error is returned, otherwise // SerializeTo will return nil. -// @ requires p.LowSerializeTo() // @ requires low(len(b)) -// @ preserves acc(p.Mem(ubuf), R1) +// @ requires acc(p.Mem(ubuf), R1) +// @ requires p.IsLow(ubuf) // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) // @ preserves sl.Bytes(b, 0, len(b)) +// @ ensures acc(p.Mem(ubuf), R1) // @ ensures r != nil ==> r.ErrorMem() // @ ensures !old(p.hasScionPath(ubuf)) ==> r != nil // @ ensures len(b) < old(p.LenSpec(ubuf)) ==> r != nil @@ -92,7 +91,8 @@ type Path struct { // @ ensures old(p.getLHVFLen(ubuf)) != HVFLen ==> r != nil // @ decreases func (p *Path) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { - // @ p.GetLowSerializeTo(ubuf, R1/2) + // p.GetLowSerializeTo(ubuf, R1/2) + //@ p.RevealIsLow(ubuf) if len(b) < p.Len( /*@ ubuf @*/ ) { return serrors.New("buffer too small to serialize path.", "expected", int(p.Len( /*@ ubuf @*/ )), "actual", len(b)) diff --git a/pkg/slayers/path/epic/epic_spec.gobra b/pkg/slayers/path/epic/epic_spec.gobra index 5082f9a82..3d7c8df5c 100644 --- a/pkg/slayers/path/epic/epic_spec.gobra +++ b/pkg/slayers/path/epic/epic_spec.gobra @@ -104,11 +104,24 @@ pure func (p *Path) IsValidResultOfDecoding(b []byte) (res bool) { (*Path) implements path.Path -pred (*Path) LowSerializeTo() +// pred (*Path) LowSerializeTo() +ghost +trusted +requires p.Mem(ub) +decreases +pure func (p *Path) IsLow(ub []byte) bool + +// ghost +// requires ep.LowSerializeTo() && p > 0 +// preserves acc(ep.Mem(buf), p) +// ensures low(ep.getPHVFLen(buf) != HVFLen) && low(ep.getLHVFLen(buf) != HVFLen) +// decreases +// func (ep *Path) GetLowSerializeTo(buf []byte, ghost p perm) ghost -requires ep.LowSerializeTo() && p > 0 -preserves acc(ep.Mem(buf), p) -ensures low(ep.getPHVFLen(buf) != HVFLen) && low(ep.getLHVFLen(buf) != HVFLen) +trusted +requires acc(p.Mem(ub), R2) && p.IsLow(ub) +ensures acc(p.Mem(ub), R2) +ensures low(p.getPHVFLen(ub) != HVFLen) && low(p.getLHVFLen(ub) != HVFLen) decreases -func (ep *Path) GetLowSerializeTo(buf []byte, ghost p perm) +func (p *Path) RevealIsLow(ub []byte) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 652fd09e1..3e23d6392 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -81,12 +81,22 @@ type Path interface { // Predicate containing sensitivity information. Intended to be implemented // as an abstract predicate, alongside an abstract function requiring // `LowSerializeTo()`, and ensuring the corresponding sensitivity. - //@ pred LowSerializeTo() - //@ requires LowSerializeTo() + // pred LowSerializeTo() + // TODO: add documentation/comment + // TODO: does it make sense to have separate IsLow* for each method here? + // TODO: at least in the future might need to add a parameter to this to + // distinguish between NonInitMem and Mem + //@ ghost + //@ requires Mem(ub) + //@ decreases + //@ pure IsLow(ghost ub []byte) bool + // requires LowSerializeTo() //@ requires low(len(b)) + //@ requires acc(Mem(ub), R1) + //@ requires IsLow(ub) //@ preserves sl.Bytes(ub, 0, len(ub)) - //@ preserves acc(Mem(ub), R1) //@ preserves sl.Bytes(b, 0, len(b)) + //@ ensures acc(Mem(ub), R1) //@ ensures e != nil ==> e.ErrorMem() //@ decreases SerializeTo(b []byte /*@, ghost ub []byte @*/) (e error) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index 883acfd2f..72f59d7b7 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -31,7 +31,12 @@ pred (r *rawPath) NonInitMem() { acc(r) } -pred (*rawPath) LowSerializeTo() +// pred (*rawPath) LowSerializeTo() +ghost +trusted +requires r.Mem(ub) +decreases +pure func (r *rawPath) IsLow(ub []byte) bool ghost requires p.Mem(buf) diff --git a/pkg/slayers/path/scion/decoded_spec.gobra b/pkg/slayers/path/scion/decoded_spec.gobra index 6329c672a..df21d2233 100644 --- a/pkg/slayers/path/scion/decoded_spec.gobra +++ b/pkg/slayers/path/scion/decoded_spec.gobra @@ -47,7 +47,12 @@ pred (d *Decoded) Mem(ubuf []byte) { (forall i int :: { &d.HopFields[i] } 0 <= i && i < len(d.HopFields) ==> d.HopFields[i].Mem()) } -pred (*Decoded) LowSerializeTo() +// pred (*Decoded) LowSerializeTo() +ghost +trusted +requires d.Mem(ub) +decreases +pure func (d *Decoded) IsLow(ub []byte) bool /**** End of Predicates ****/ diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index fb43686b1..1d8436068 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -38,7 +38,12 @@ pred (s *Raw) Mem(buf []byte) { len(s.Raw) == s.Base.Len() } -pred (s *Raw) LowSerializeTo() +// pred (s *Raw) LowSerializeTo() +ghost +trusted +requires s.Mem(ub) +decreases +pure func (s *Raw) IsLow(ub []byte) bool /**** End of Predicates ****/ (*Raw) implements path.Path From 5446132cd74d90563425ae5d9c8dd86d90c91d8b Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 17 Jul 2025 22:19:24 +0200 Subject: [PATCH 066/104] Improve stability and make BatchConn match --- private/underlay/conn/conn.go | 4 ++-- router/dataplane.go | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 6f2375cf5..cb7916b8e 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -484,8 +484,8 @@ func NewReadMessages(n int) (res Messages) { m := make(Messages, n) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() && m[j].GetAddr() == nil //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) && m[j].N == 0 - //@ invariant forall j int :: { m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil - //@ invariant forall j int :: { m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil + //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil + //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil //@ invariant low(i0) //@ decreases len(m) - i for i := range m /*@ with i0 @*/ { diff --git a/router/dataplane.go b/router/dataplane.go index 7ae5518df..bcffca54a 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -148,6 +148,11 @@ type bfdSession interface { // with the same name in private/underlay/conn/Conn. type BatchConn interface { // @ pred Mem() + + // @ ghost + // @ requires Mem() + // @ decreases + // @ pure IsLow() bool // @ requires acc(Mem(), _) // @ requires forall i int :: { &msgs[i] } 0 <= i && i < len(msgs) ==> @@ -188,7 +193,7 @@ type BatchConn interface { // @ low(old(MultiReadBioIO_val(place, n)[i])) ReadBatch(msgs underlayconn.Messages /*@, ghost ingressID uint16, ghost prophecyM int, ghost place io.Place @*/) (n int, err error) // @ requires acc(addr.Mem(), _) - // @ requires acc(Mem(), _) + // @ requires acc(Mem(), _) && IsLow() // @ preserves acc(sl.Bytes(b, 0, len(b)), R10) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() @@ -216,7 +221,7 @@ type BatchConn interface { // otherwise the router cannot continue after failing to send a packet. // @ ensures io.token(old(io.dp3s_iospec_bio3s_send_T(place, ioAbsPkts))) WriteBatch(msgs underlayconn.Messages, flags int /*@, ghost egressID uint16, ghost place io.Place, ghost ioAbsPkts io.Val @*/) (n int, err error) - // @ requires Mem() + // @ requires Mem() && IsLow() // @ ensures err != nil ==> err.ErrorMem() // @ decreases Close() (err error) From a05c2a00ffad9bac720a7d6db596d567407a864d Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 24 Jul 2025 17:04:36 +0200 Subject: [PATCH 067/104] "Introduce body" to `isZeros` and improve stability of `NewReadMessages` `isZeros` now has accompanying `isZeroSpec` with implementation s.t. one can infer `low(isZeros(s))` if the elements of `s` are low. `NewReadMessages` has different triggers to hopefully improve stability (Joao's suggestion). Cannot really test yet, since package is now failing (cf. Gobra issue #948) --- private/underlay/conn/conn.go | 21 +++++++++++++++++++-- verification/dependencies/net/ip.gobra | 12 ++++++++++-- verification/dependencies/net/udpsock.gobra | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index cb7916b8e..476d2361e 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -136,8 +136,21 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { // @ a.RevealIsLow() // @ assert len(a.GetIP()) == net.IPv6len ==> // @ low(a.GetIPByte(10) == 255) && low(a.GetIPByte(11) == 255) + // @ ghost if len(a.GetIP()) == net.IPv6len { + // @ ghost a.GetIPByte(0) + // @ ghost a.GetIPByte(1) + // @ ghost a.GetIPByte(2) + // @ ghost a.GetIPByte(3) + // @ ghost a.GetIPByte(4) + // @ ghost a.GetIPByte(5) + // @ ghost a.GetIPByte(6) + // @ ghost a.GetIPByte(7) + // @ ghost a.GetIPByte(8) + // @ ghost a.GetIPByte(9) + // @ } // @ unfold acc(a.Mem(), R15) // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15) + // @ assert len(a.GetIP()) == net.IPv6len ==> low(net.isZeros(a.IP[0:10])) if a.IP.To4( /*@ false @*/ ) != nil { return newConnUDPIPv4(listen, remote, cfg) } @@ -482,8 +495,10 @@ func (c *connUDPBase) Close() (err error) { // @ decreases func NewReadMessages(n int) (res Messages) { m := make(Messages, n) - //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() && m[j].GetAddr() == nil - //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) && m[j].N == 0 + //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() + //@ invariant forall j int :: { m[j].GetAddr() } (0 <= j && j < i0) ==> m[j].GetAddr() == nil + //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) + //@ invariant forall j int :: { &m[j].N } (i0 <= j && j < len(m)) ==> m[j].N == 0 //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil //@ invariant low(i0) @@ -493,7 +508,9 @@ func NewReadMessages(n int) (res Messages) { m[i].Buffers = make([][]byte, 1) //@ fold sl.Bytes(m[i].Buffers[0], 0, len(m[i].Buffers[0])) //@ fold sl.Bytes(m[i].OOB, 0, len(m[i].OOB)) + //@ BeforeFold: //@ fold m[i].Mem() + //@ assert forall j int :: { m[j].GetAddr() } 0 <= j && j < i0 ==> old[BeforeFold](m[j].GetAddr()) == nil } return m } diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 3f6dad638..a9ac1d9fd 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -93,9 +93,17 @@ func (ip IP) To4(ghost wildcard bool) (res IP) { return nil } -preserves forall i int :: { &s[i] } 0 <= i && i < len(s) ==> acc(&s[i]) +ghost +requires forall i int :: { &s[i] } 0 <= i && i < len(s) ==> acc(&s[i]) decreases -pure func isZeros(s []byte) bool +pure func isZerosSpec(s []byte) bool { + return forall i int :: { &s[i] } 0 <= i && i < len(s) ==> s[i] == 0 +} + +requires forall i int :: { &s[i] } 0 <= i && i < len(s) ==> acc(&s[i]) +ensures res == isZerosSpec(s) +decreases +pure func isZeros(s []byte) (res bool) // To16 converts the IP address ip to a 16-byte representation. requires low(len(ip)) diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index 63dd783eb..1032c9805 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -61,7 +61,7 @@ ensures forall i int :: { a.GetIPByte(i) } 0 <= i && i < len(a.GetIP()) ==> low(a.GetIPByte(i)) // Given that all elements of `a.IP` are low, this should be implied; // however, without a body for `isZeros`, we state it here explicitly. -ensures len(a.GetIP()) == IPv6len ==> low(isZeros(a.GetIP()[0:10])) +// ensures len(a.GetIP()) == IPv6len ==> low(isZeros(a.GetIP()[0:10])) decreases func (a *UDPAddr) RevealIsLow() From 0a210ed517ac6881dc1dd7c7ee9e40c97ca5dd3b Mon Sep 17 00:00:00 2001 From: henriman Date: Fri, 25 Jul 2025 13:46:42 +0200 Subject: [PATCH 068/104] Avoid requiring input of `fmt.Sprintf` to be low As `Sprintf` takes input of type `interface{}`, which may also contain pointer types, it is not clear how to require the input to be low. --- pkg/private/serrors/serrors_spec.gobra | 4 ---- private/topology/underlay/defs.go | 2 ++ verification/dependencies/fmt/fmt.gobra | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 1e4e0ab99..c08711af8 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -90,10 +90,6 @@ func WrapStr(msg string, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// TODO: For completeness, we need to require every value that influences -// control flow (in the implementation of `New`) to be low. This annotation is -// likely not sufficient. -requires low(len(errCtx)) preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) ensures res != nil && res.ErrorMem() ensures res.IsDuplicableMem() diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 286f953eb..2885db386 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -48,6 +48,7 @@ const ( ) // @ requires low(o) +// @ requires o == UDPIPv4 || o == UDPIPv6 || o == UDPIPv46 // @ ensures low(res) func (o Type) String() (res string) { switch o { @@ -58,6 +59,7 @@ func (o Type) String() (res string) { case UDPIPv46: return UDPIPv46Name default: + // @ Unreachable() return fmt.Sprintf("UNKNOWN (%d)", o) } } diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 84d0cb14b..214bee3fd 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -24,8 +24,8 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) // TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `v` // using a pure Boolean function. -ensures (low(format) && low(len(v)) && - forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) +// ensures (low(format) && low(len(v)) && +// forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From 4457ac8c454924c6d3a82a699502bbe6fa611a48 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 30 Jul 2025 22:30:18 +0200 Subject: [PATCH 069/104] Add comments --- verification/dependencies/flag/flag.gobra | 2 ++ verification/dependencies/fmt/fmt.gobra | 2 ++ 2 files changed, 4 insertions(+) diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 9a74520b9..571018005 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -26,6 +26,8 @@ type Value interface { // both require a function like `IsLow`. Currently, Gobra does not allow // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. // Consequently, we require separate definitions for each interface. + // (However, these definitions can then call a common `IsLow` function, as + // exemplified in `pkg/addr/isdas_spec.gobra`). ghost requires Mem() decreases diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 2750a3d0c..ee37d0e48 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -39,6 +39,8 @@ type Stringer interface { // both require a function like `IsLow`. Currently, Gobra does not allow // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. // Consequently, we require separate definitions for each interface. + // (However, these definitions can then call a common `IsLow` function, as + // exemplified in `pkg/addr/isdas_spec.gobra`). ghost requires Mem() decreases From 2f96475f919ed8032f02fd380b3dcea23b3322c6 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 31 Jul 2025 19:00:19 +0200 Subject: [PATCH 070/104] Clean up --- pkg/addr/fmt.go | 5 +-- pkg/addr/host.go | 29 +++++++------- pkg/addr/host_spec.gobra | 24 +++++++----- pkg/addr/isdas.go | 38 +++++++------------ pkg/addr/isdas_spec.gobra | 29 -------------- pkg/private/serrors/serrors_spec.gobra | 7 ++-- verification/dependencies/flag/flag.gobra | 19 +--------- verification/dependencies/fmt/fmt.gobra | 17 +-------- verification/dependencies/net/ip.gobra | 21 ++++++---- .../dependencies/strings/builder.gobra | 15 +------- .../dependencies/strings/strings.gobra | 6 +-- 11 files changed, 71 insertions(+), 139 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index a689cf172..3b8c01c34 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -113,9 +113,8 @@ func FormatAS(as_ AS, opts ...FormatOption) string { // @ requires as_.inRange() // @ requires low(as_) && low(sep) -// @ ensures low(res) // @ decreases -func fmtAS(as_ AS, sep string) (res string) { +func fmtAS(as_ AS, sep string) string { if !as_.inRange() { return fmt.Sprintf("%d [Illegal AS: larger than %d]", as_, MaxAS) } @@ -134,7 +133,7 @@ func fmtAS(as_ AS, sep string) (res string) { // @ b.ZeroBuilderIsReadyToUse() b.Grow(maxLen) // @ invariant b.Mem() - // @ invariant low(i) && b.IsLow() + // @ invariant low(i) // @ decreases asParts - i for i := 0; i < asParts; i++ { if i > 0 { diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 3b2594eb4..11ef26e52 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -125,15 +125,16 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer - // Return whether all the underlying data used in the computation of - // `String` is low. + // NOTE[henri]: I rewrote this comment to be more accurate + // Return whether all the underlying data that needs to be low for the + // computation of `String` is low. //@ ghost //@ requires Mem() //@ decreases //@ pure IsLow() bool //@ requires acc(Mem(), R13) && IsLow() - //@ ensures acc(Mem(), R13) + //@ ensures acc(Mem(), R13) //@ decreases String() string } @@ -175,7 +176,7 @@ func (h HostNone) Copy() (res HostAddr) { // The Viper encoding branches on `typeOf(o)`. // @ requires low(typeOf(o)) -// @ ensures res == (typeOf(o) == type[HostNone]) +// @ ensures res == (typeOf(o) == type[HostNone]) // @ decreases func (h HostNone) Equal(o HostAddr) (res bool) { _, ok := o.(HostNone) @@ -233,7 +234,7 @@ func (h HostIPv4) Copy() (res HostAddr) { return tmp } -// @ requires low(typeOf(o)) +// @ requires low(typeOf(o)) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -302,7 +303,7 @@ func (h HostIPv6) Copy() (res HostAddr) { return tmp } -// @ requires low(typeOf(o)) +// @ requires low(typeOf(o)) // @ preserves acc(h.Mem(), R13) // @ preserves acc(o.Mem(), R13) // @ decreases @@ -388,15 +389,15 @@ func (h HostSVC) IP() (res net.IP) { return nil } -// @ ensures low(h) ==> low(res) // @ decreases -func (h HostSVC) IsMulticast() (res bool) { +// @ pure +func (h HostSVC) IsMulticast() bool { return (h & SVCMcast) != 0 } -// @ ensures low(h) ==> low(res) // @ decreases -func (h HostSVC) Base() (res HostSVC) { +// @ pure +func (h HostSVC) Base() HostSVC { return h & ^SVCMcast } @@ -435,9 +436,8 @@ func (h HostSVC) String() string { // BaseString returns the upper case name of the service. For hosts or unrecognized services, it // returns UNKNOWN. // @ requires low(h) -// @ ensures low(res) // @ decreases -func (h HostSVC) BaseString() (res string) { +func (h HostSVC) BaseString() string { switch h.Base() { case SvcDS: return "DS" @@ -501,7 +501,7 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 // @ requires low(len(ip)) && (len(ip) == HostLenIPv6 ==> // @ low(net.isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) -// @ ensures res.Mem() +// @ ensures res.Mem() // @ decreases func HostFromIP(ip net.IP) (res HostAddr) { if ip4 := ip.To4( /*@ false @*/ ); ip4 != nil { @@ -517,7 +517,7 @@ func HostFromIP(ip net.IP) (res HostAddr) { } // @ requires low(s) -// @ ensures res.Mem() +// @ ensures res.Mem() // @ decreases func HostFromIPStr(s string) (res HostAddr) { ip := net.ParseIP(s) @@ -526,6 +526,7 @@ func HostFromIPStr(s string) (res HostAddr) { //@ fold tmp.Mem() return tmp } + assert len(ip) > 0 ==> low(ip[0]) return HostFromIP(ip) } diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index e9e0253a3..8fb574997 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -25,12 +25,14 @@ import ( pred (h HostNone) Mem() { len(h) == HostLenNone } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +// NOTE[henri]: As `HostNone.String` doesn't need anything to be low, I +// implemented it trivially already. ghost -trusted requires h.Mem() decreases -pure func (h HostNone) IsLow() bool +pure func (h HostNone) IsLow() bool { + return true +} HostNone implements HostAddr @@ -39,12 +41,14 @@ pred (h HostIPv4) Mem() { slices.Bytes(h, 0, len(h)) } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +// NOTE[henri]: As `HostIPv4.String` doesn't need anything to be low, I +// implemented it trivially already. ghost -trusted requires h.Mem() decreases -pure func (h HostIPv4) IsLow() bool +pure func (h HostIPv4) IsLow() bool { + return true +} HostIPv4 implements HostAddr @@ -53,12 +57,14 @@ pred (h HostIPv6) Mem() { slices.Bytes(h, 0, len(h)) } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. +// NOTE[henri]: As `HostIPv6.String` doesn't need anything to be low, I +// implemented it trivially already. ghost -trusted requires h.Mem() decreases -pure func (h HostIPv6) IsLow() bool +pure func (h HostIPv6) IsLow() bool { + return true +} HostIPv6 implements HostAddr diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 537025dd8..0be19b0a7 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -50,7 +50,7 @@ type ISD uint16 // ParseISD parses an ISD from a decimal string. Note that ISD 0 is parsed // without any errors. // @ requires low(s) -// @ ensures low(retISD) && low(retErr != nil) +// @ ensures low(retISD) && low(retErr != nil) // @ decreases func ParseISD(s string) (retISD ISD, retErr error) { isd, err := strconv.ParseUint(s, 10, ISDBits) @@ -75,16 +75,16 @@ type AS uint64 // ParseAS parses an AS from a decimal (in the case of the 32bit BGP AS number // space) or ipv6-style hex (in the case of SCION-only AS numbers) string. // @ requires low(_as) -// @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr != nil) +// @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func ParseAS(_as string) (retAs AS, retErr error) { return parseAS(_as, ":") } // @ requires low(_as) && low(sep) -// @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr != nil) +// @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func parseAS(_as string, sep string) (retAs AS, retErr error) { parts := strings.Split(_as, sep) @@ -120,8 +120,8 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { } // @ requires low(s) -// @ ensures retErr == nil ==> retAs.inRange() -// @ ensures low(retAs) && low(retErr != nil) +// @ ensures retErr == nil ==> retAs.inRange() +// @ ensures low(retAs) && low(retErr != nil) // @ decreases func asParseBGP(s string) (retAs AS, retErr error) { _as, err := strconv.ParseUint(s, 10, BGPASBits) @@ -172,7 +172,7 @@ func (_as AS) MarshalText() ([]byte, error) { // @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases func (_as *AS) UnmarshalText(text []byte) error { - //@ sif.LowSliceImpliesLowString(text, 1/1) + //@ sif.LowSliceImpliesLowString(text, writePerm) parsed, err := ParseAS(string(text)) if err != nil { return err @@ -193,8 +193,7 @@ type IA uint64 // is encountered. Callers must ensure that the values passed to this function // are valid. // @ requires _as.inRange() -// @ requires low(_as.inRange()) -// @ ensures low(isd) && low(_as) ==> low(res) +// @ ensures low(isd) && low(_as) ==> low(res) // @ decreases func MustIAFrom(isd ISD, _as AS) (res IA) { ia, err := IAFrom(isd, _as) @@ -206,9 +205,8 @@ func MustIAFrom(isd ISD, _as AS) (res IA) { // IAFrom creates an IA from the ISD and AS number. // @ requires _as.inRange() -// @ requires low(_as.inRange()) -// @ ensures err == nil -// @ ensures low(isd) && low(_as) ==> low(ia) +// @ ensures err == nil +// @ ensures low(isd) && low(_as) ==> low(ia) // @ decreases func IAFrom(isd ISD, _as AS) (ia IA, err error) { if !_as.inRange() { @@ -219,7 +217,7 @@ func IAFrom(isd ISD, _as AS) (ia IA, err error) { // ParseIA parses an IA from a string of the format 'isd-as'. // @ requires low(ia) -// @ ensures low(retErr != nil) +// @ ensures low(retErr != nil) // @ decreases func ParseIA(ia string) (retIA IA, retErr error) { parts := strings.Split(ia, "-") @@ -249,7 +247,6 @@ func (ia IA) AS() AS { return AS(ia) & MaxAS } -// @ requires ia.IsLow() // @ decreases func (ia IA) MarshalText() ([]byte, error) { return []byte(ia.String()), nil @@ -262,7 +259,7 @@ func (ia IA) MarshalText() ([]byte, error) { // @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases func (ia *IA) UnmarshalText(b []byte) error { - //@ sif.LowSliceImpliesLowString(b, 1/1) + //@ sif.LowSliceImpliesLowString(b, writePerm) parsed, err := ParseIA(string(b)) if err != nil { return err @@ -290,21 +287,14 @@ func (ia IA) IsWildcard() bool { return ia.ISD() == 0 || ia.AS() == 0 } -// @ requires ia.IsLow() // @ decreases func (ia IA) String() string { - //@ ia.RevealIsLow() // (VerifiedSCION) Added casts around ia.ISD() and ia.AS() to be able to pass them to 'fmt.Sprintf' - // TODO: Once Gobra issue #835/#890 is resolved, remove this assumption. - //@ ghost v := []interface{}{ia.ISD(), ia.AS()} - //@ assert low(v[0]) - //@ assert low(v[1]) - //@ assume forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i]) && low(v[i]) return fmt.Sprintf("%d-%s", ia.ISD(), ia.AS()) } // Set implements flag.Value interface -// @ requires low(s) +// @ requires low(s) // @ preserves acc(ia) // @ decreases func (ia *IA) Set(s string) error { diff --git a/pkg/addr/isdas_spec.gobra b/pkg/addr/isdas_spec.gobra index d41721572..3bd12dd19 100644 --- a/pkg/addr/isdas_spec.gobra +++ b/pkg/addr/isdas_spec.gobra @@ -24,13 +24,6 @@ import ( pred (ia *IA) Mem() { acc(ia) } -ghost -requires ia.Mem() -decreases -pure func (ia *IA) IsLowForValue() bool { - return unfolding ia.Mem() in ia.IsLow() -} - (*IA) implements encoding.TextUnmarshaler { (ia *IA) UnmarshalText(text []byte) (err error) { unfold ia.Mem() @@ -43,28 +36,6 @@ pure func (ia *IA) IsLowForValue() bool { // Issue: https://github.com/viperproject/gobra/issues/449 pred MemForStringer(ia IA) { true } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. -ghost -trusted -decreases -pure func (ia IA) IsLow() bool - -ghost -requires MemForStringer(ia) -decreases -pure func (ia IA) IsLowForStringer() bool { - return ia.IsLow() -} - -// "Reveal" the "body" of `ia.IsLow` (necessary bc. we can't implement `ia.IsLow` yet). -// TODO: Once Gobra issue #846 is resolved, implement this. -ghost -trusted -requires ia.IsLow() -ensures low(ia) -decreases -func (ia IA) RevealIsLow() - IA implements fmt.Stringer { pred Mem := MemForStringer diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index e1da5f093..7c6bd2518 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,10 +76,9 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// TODO: For completeness, we need to require every value that influences -// control flow (in the implementation of `WrapStr`) to be low. This annotation -// is not sufficient. -requires low(len(errCtx)) +// NOTE[henri]: As discussed, I took out annotations such as `low(len(errCtx))` +// that were intended to satisfy the technical requirement of the simple +// encoding that control flow has to be low. requires cause != nil ==> cause.ErrorMem() preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. diff --git a/verification/dependencies/flag/flag.gobra b/verification/dependencies/flag/flag.gobra index 571018005..1fb4ccfb8 100644 --- a/verification/dependencies/flag/flag.gobra +++ b/verification/dependencies/flag/flag.gobra @@ -19,27 +19,12 @@ package flag type Value interface { pred Mem() - // Return whether all the underlying data used in the computation of - // `String` is low. - // NOTE: The reason we don't call this function `IsLow` is that in pkg/addr, - // `IA` implements `fmt.Stringer`, and `*IA` implements `flag.Value` - which - // both require a function like `IsLow`. Currently, Gobra does not allow - // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. - // Consequently, we require separate definitions for each interface. - // (However, these definitions can then call a common `IsLow` function, as - // exemplified in `pkg/addr/isdas_spec.gobra`). - ghost - requires Mem() - decreases - pure IsLowForValue() bool - - requires Mem() && IsLowForValue() - ensures Mem() + preserves Mem() decreases String() string requires low(s) - preserves acc(Mem()) + preserves Mem() decreases Set(s string) error } diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index ee37d0e48..12c5c40b6 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -32,22 +32,7 @@ func Sprintf(format string, v ...interface{}) (res string) type Stringer interface { pred Mem() - // Return whether all the underlying data used in the computation of - // `String` is low. - // NOTE: The reason we don't call this function `IsLow` is that in pkg/addr, - // `IA` implements `fmt.Stringer`, and `*IA` implements `flag.Value` - which - // both require a function like `IsLow`. Currently, Gobra does not allow - // "sharing" one definition of `IsLow`; Gobra issue #939 is related to this. - // Consequently, we require separate definitions for each interface. - // (However, these definitions can then call a common `IsLow` function, as - // exemplified in `pkg/addr/isdas_spec.gobra`). - ghost - requires Mem() - decreases - pure IsLowForStringer() bool - - requires Mem() && IsLowForStringer() - ensures Mem() + preserves Mem() decreases String() string } diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 99ba4b018..d4bb62a5a 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -61,11 +61,11 @@ preserves forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R15) func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. -requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) +requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) requires !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) requires low(len(ip)) && (len(ip) == IPv6len ==> low(isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) -ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) +ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) ensures !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len ensures len(ip) == IPv4len ==> ip === res @@ -78,7 +78,7 @@ ensures (len(ip) == IPv6len && res != nil) ==> let _ := sl.AssertSliceOverlap(ip, 12, 16) in res === ip[12:16] ensures len(ip) != IPv4len && len(ip) != IPv6len ==> res == nil -ensures low(res != nil) +ensures low(res != nil) decreases func (ip IP) To4(ghost wildcard bool) (res IP) { if len(ip) == IPv4len { @@ -93,9 +93,17 @@ func (ip IP) To4(ghost wildcard bool) (res IP) { return nil } +ghost requires forall i int :: { &s[i] } 0 <= i && i < len(s) ==> acc(&s[i]) decreases -pure func isZeros(s []byte) bool +pure func isZerosSpec(s []byte) bool { + return forall i int :: { &s[i] } 0 <= i && i < len(s) ==> s[i] == 0 +} + +requires forall i int :: { &s[i] } 0 <= i && i < len(s) ==> acc(&s[i]) +ensures res == isZerosSpec(s) +decreases +pure func isZeros(s []byte) (res bool) // To16 converts the IP address ip to a 16-byte representation. requires low(len(ip)) @@ -149,8 +157,7 @@ pure func (ip IP) Equal(x IP) bool // ParseIP parses s as an IP address, returning the result. ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len -ensures low(s) ==> - (low(res == nil) && low(len(res)) && (len(res) == IPv6len ==> - low(isZeros(res[0:10])) && low(res[10] == 255) && low(res[11] == 255))) +ensures low(s) ==> low(res == nil) && low(len(res)) && + forall i int :: { &res[i] } 0 <= i && i < len(res) ==> low(res[i]) decreases _ func ParseIP(s string) (res IP) diff --git a/verification/dependencies/strings/builder.gobra b/verification/dependencies/strings/builder.gobra index 917998d54..e614b1726 100644 --- a/verification/dependencies/strings/builder.gobra +++ b/verification/dependencies/strings/builder.gobra @@ -19,35 +19,24 @@ type Builder struct { pred (b *Builder) Mem() -// Return whether the underlying data is low. -ghost -trusted -requires b.Mem() -decreases -pure func (b *Builder) IsLow() bool - // String returns the accumulated string. preserves b.Mem() -ensures old(b.IsLow()) == b.IsLow() -ensures b.IsLow() ==> low(str) decreases _ -func (b *Builder) String() (str string) +func (b *Builder) String() string requires 0 <= n preserves b.Mem() -ensures old(b.IsLow()) && low(n) ==> b.IsLow() decreases _ func (b *Builder) Grow(n int) preserves b.Mem() ensures err == nil -ensures low(s) && old(b.IsLow()) ==> b.IsLow() && low(n) decreases _ func (b *Builder) WriteString(s string) (n int, err error) ghost requires acc(b) requires *b === Builder{} -ensures b.Mem() && b.IsLow() +ensures b.Mem() decreases _ func (b *Builder) ZeroBuilderIsReadyToUse() \ No newline at end of file diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index ea4520988..e2d645042 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -66,7 +66,8 @@ func SplitAfterN(s, sep string, n int) (res []string) // Split slices s into all substrings separated by sep and returns a slice of // the substrings between those separators. -ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i]) && ((low(s) && low(sep)) ==> low(res[i])) +ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> + acc(&res[i]) && ((low(s) && low(sep)) ==> low(res[i])) ensures (low(s) && low(sep)) ==> low(len(res)) decreases _ func Split(s, sep string) (res []string) //{ return genSplit(s, sep, 0, -1) } @@ -158,9 +159,8 @@ func TrimPrefix(s, prefix string) string // TrimSuffix returns s without the provided trailing suffix string. // If s doesn't end with suffix, s is returned unchanged. -ensures low(s) && low(suffix) ==> low(res) decreases _ -func TrimSuffix(s, suffix string) (res string) +pure func TrimSuffix(s, suffix string) string // Replace returns a copy of the string s with the first n // non-overlapping instances of old replaced by new. From 20a1b8fba797d2c4305d4151d09df071cd7e5fac Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 31 Jul 2025 19:49:00 +0200 Subject: [PATCH 071/104] Add //@ before assert --- pkg/addr/host.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 11ef26e52..e6281225f 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -526,7 +526,7 @@ func HostFromIPStr(s string) (res HostAddr) { //@ fold tmp.Mem() return tmp } - assert len(ip) > 0 ==> low(ip[0]) + //@ assert len(ip) > 0 ==> low(ip[0]) return HostFromIP(ip) } From 2125136fa54612dc1e74b4807262fecd4ee519a2 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 31 Jul 2025 19:50:42 +0200 Subject: [PATCH 072/104] Clean up --- verification/dependencies/fmt/fmt.gobra | 4 ---- 1 file changed, 4 deletions(-) diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 214bee3fd..70433af18 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -22,10 +22,6 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // TODO: // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) -// TODO: Once Gobra issue #846 is resolved, capture the sensitivity of `v` -// using a pure Boolean function. -// ensures (low(format) && low(len(v)) && -// forall i int :: { &v[i] } 0 <= i && i < len(v) ==> low(v[i])) ==> low(res) decreases _ func Sprintf(format string, v ...interface{}) (res string) From b4084b5bc42c1f980aa348d6f9c28cb05947d2ff Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 31 Jul 2025 19:51:27 +0200 Subject: [PATCH 073/104] Clean up --- verification/dependencies/fmt/fmt.gobra | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/dependencies/fmt/fmt.gobra b/verification/dependencies/fmt/fmt.gobra index 70433af18..eefea525f 100644 --- a/verification/dependencies/fmt/fmt.gobra +++ b/verification/dependencies/fmt/fmt.gobra @@ -23,7 +23,7 @@ preserves forall i int :: { &v[i] } 0 <= i && i < len(v) ==> acc(&v[i], R55) // The following precondition cannot be adequately captured in Gobra. // preserves forall i int :: 0 <= i && i < len(v) ==> definitions.IsOfPrimitiveType(v[i]) decreases _ -func Sprintf(format string, v ...interface{}) (res string) +func Sprintf(format string, v ...interface{}) string type Stringer interface { pred Mem() From 7bf7104f5c44e54dad3cdd6a64739e9c537f824f Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 5 Aug 2025 19:34:19 +0200 Subject: [PATCH 074/104] Clean up further and add notes (NOTE[henri]) --- private/underlay/conn/conn.go | 31 +++++++++++++++++---- private/underlay/conn/conn_spec.gobra | 4 +++ verification/dependencies/net/udpsock.gobra | 10 +++---- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 476d2361e..7b0a74142 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -34,7 +34,6 @@ import ( "github.com/scionproto/scion/private/underlay/sockctrl" //@ . "github.com/scionproto/scion/verification/utils/definitions" //@ sl "github.com/scionproto/scion/verification/utils/slices" - //@ "github.com/scionproto/scion/verification/utils/sif" ) // Messages is a list of ipX.Messages. It is necessary to hide the type alias @@ -44,10 +43,12 @@ type Messages []ipv4.Message // Conn describes the API for an underlay socket type Conn interface { //@ pred Mem() + //@ ghost //@ requires Mem() //@ decreases //@ pure IsLow() bool + // (VerifiedSCION) Reads a message to b. Returns the number of read bytes. //@ requires acc(Mem(), _) //@ preserves sl.Bytes(b, 0, len(b)) @@ -133,9 +134,13 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { } // @ assert remote != nil ==> a == remote // @ assert remote == nil ==> a == listen + // NOTE[henri]: It seems that we have to call `a.GetIPByte(i)` for all of + // these `i`. I could make this (somewhat) cleaner by replacing the + // individual calls with assertions that contain multiple calls, such as + // `assert low(a.GetIPByte(0)) && low(a.GetIPByte(1)) && ...`, and/or + // by introducing an auxiliary function which gives us + // `(forall i int :: low(a.GetIPByte(i))) ==> low(isZeros(ip[0:10])) && low(ip[10] == 255) && ...` // @ a.RevealIsLow() - // @ assert len(a.GetIP()) == net.IPv6len ==> - // @ low(a.GetIPByte(10) == 255) && low(a.GetIPByte(11) == 255) // @ ghost if len(a.GetIP()) == net.IPv6len { // @ ghost a.GetIPByte(0) // @ ghost a.GetIPByte(1) @@ -147,10 +152,11 @@ func New(listen, remote *net.UDPAddr, cfg *Config) (res Conn, e error) { // @ ghost a.GetIPByte(7) // @ ghost a.GetIPByte(8) // @ ghost a.GetIPByte(9) + // @ ghost a.GetIPByte(10) + // @ ghost a.GetIPByte(11) // @ } // @ unfold acc(a.Mem(), R15) // @ unfold acc(sl.Bytes(a.IP, 0, len(a.IP)), R15) - // @ assert len(a.GetIP()) == net.IPv6len ==> low(net.isZeros(a.IP[0:10])) if a.IP.To4( /*@ false @*/ ) != nil { return newConnUDPIPv4(listen, remote, cfg) } @@ -487,7 +493,10 @@ func (c *connUDPBase) Close() (err error) { // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. -// NOTE: The verification of this function is unstable. +// NOTE[henri]: The verification of this function is still somewhat unstable, +// although adjusting the triggers made it significantly more stable and it now +// verifies successfully the majority of the time. I'm not sure how to improve +// this further. // @ requires 0 < n // @ requires low(n) // @ ensures len(res) == n @@ -497,7 +506,17 @@ func NewReadMessages(n int) (res Messages) { m := make(Messages, n) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() //@ invariant forall j int :: { m[j].GetAddr() } (0 <= j && j < i0) ==> m[j].GetAddr() == nil - //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) + // NOTE[henri]: Splitting up the following invariant has only marginally + // improved stability (if at all), thus we could consider undoing this. + // invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) + //@ invariant forall j int :: { &m[j].Buffers } (i0 <= j && j < len(m)) ==> acc(&m[j].Buffers) + //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> acc(&m[j].OOB) + //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> acc(&m[j].Addr) + //@ invariant forall j int :: { &m[j].N } (i0 <= j && j < len(m)) ==> acc(&m[j].N) + //@ invariant forall j int :: { &m[j].NN } (i0 <= j && j < len(m)) ==> acc(&m[j].NN) + //@ invariant forall j int :: { &m[j].Flags } (i0 <= j && j < len(m)) ==> acc(&m[j].Flags) + //@ invariant forall j int :: { &m[j].IsActive } (i0 <= j && j < len(m)) ==> acc(&m[j].IsActive) + //@ invariant forall j int :: { &m[j].WildcardPerm } (i0 <= j && j < len(m)) ==> acc(&m[j].WildcardPerm) //@ invariant forall j int :: { &m[j].N } (i0 <= j && j < len(m)) ==> m[j].N == 0 //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index da2ad65e2..354018783 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -40,6 +40,10 @@ pred (c *connUDPBase) MemWithoutConn() { (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } +// NOTE[henri]: Once Gobra issue #883 is resolved, we could remove `GetClosed` +// etc. in favor of unfolding `Mem` in the contract of `RevealIsLow` etc. +// directly. However, I'm not sure whether that is preferrable. If it is, add +// a corresponding TODO here. ghost requires withConn ==> c.Mem() requires !withConn ==> c.MemWithoutConn() diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index 1032c9805..e3e0f01af 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -13,7 +13,6 @@ import "time" import . "github.com/scionproto/scion/verification/utils/definitions" import sl "github.com/scionproto/scion/verification/utils/slices" -import "github.com/scionproto/scion/verification/utils/sif" // UDPAddr represents the address of a UDP end point. type UDPAddr struct { @@ -28,6 +27,10 @@ pred (a *UDPAddr) Mem() { acc(a, R5) && sl.Bytes(a.IP, 0, len(a.IP)) } +// NOTE[henri]: Once Gobra issue #883 is resolved, we could remove `GetIP` +// etc. in favor of unfolding `Mem` in the contract of `RevealIsLow` directly. +// However, I'm not sure whether that is preferrable. If it is, add a +// corresponding TODO here. requires a.Mem() decreases pure func (a *UDPAddr) GetIP() IP { @@ -59,9 +62,6 @@ ensures acc(a.Mem(), R15) ensures low(len(a.GetIP())) ensures forall i int :: { a.GetIPByte(i) } 0 <= i && i < len(a.GetIP()) ==> low(a.GetIPByte(i)) -// Given that all elements of `a.IP` are low, this should be implied; -// however, without a body for `isZeros`, we state it here explicitly. -// ensures len(a.GetIP()) == IPv6len ==> low(isZeros(a.GetIP()[0:10])) decreases func (a *UDPAddr) RevealIsLow() @@ -96,7 +96,7 @@ pred (u *UDPConn) Mem() { // TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. // - Here, `IsLow` will need to cover all fields (of `UDPConn`), since we use -// this information in private/underlay/sockctrl/sockopt.go, GetsockoptInt. +// this information in private/underlay/sockctrl/sockopt.go, `GetsockoptInt`. ghost trusted requires c.Mem() From 8f64df5c9f1ffb156f2962bd9b4581937bb1e643 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 5 Aug 2025 21:03:26 +0200 Subject: [PATCH 075/104] Address PR feedback --- pkg/addr/fmt.go | 2 +- pkg/addr/host.go | 4 ++-- verification/dependencies/net/ip.gobra | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/addr/fmt.go b/pkg/addr/fmt.go index 3b8c01c34..a86f617fd 100644 --- a/pkg/addr/fmt.go +++ b/pkg/addr/fmt.go @@ -112,7 +112,7 @@ func FormatAS(as_ AS, opts ...FormatOption) string { } // @ requires as_.inRange() -// @ requires low(as_) && low(sep) +// @ requires low(as_) // @ decreases func fmtAS(as_ AS, sep string) string { if !as_.inRange() { diff --git a/pkg/addr/host.go b/pkg/addr/host.go index e6281225f..916fc1aca 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -499,8 +499,8 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { // @ requires acc(ip) // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 -// @ requires low(len(ip)) && (len(ip) == HostLenIPv6 ==> -// @ low(net.isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) +// @ requires low(len(ip)) && (len(ip) == HostLenIPv6 ==> +// @ forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i])) // @ ensures res.Mem() // @ decreases func HostFromIP(ip net.IP) (res HostAddr) { diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index d4bb62a5a..5a958864a 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -63,8 +63,8 @@ func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) requires !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) -requires low(len(ip)) && (len(ip) == IPv6len ==> - low(isZeros(ip[0:10])) && low(ip[10] == 255) && low(ip[11] == 255)) +requires low(len(ip)) && (len(ip) == IPv6len ==> + forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i])) ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) ensures !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len @@ -84,6 +84,10 @@ func (ip IP) To4(ghost wildcard bool) (res IP) { if len(ip) == IPv4len { return ip } + // This assertion is ecessary to infer `low(isZeros(ip[0:10]))`. + assert len(ip) == IPv6len ==> + low(ip[0]) && low(ip[1]) && low(ip[2]) && low(ip[3]) && low(ip[4]) && + low(ip[5]) && low(ip[6]) && low(ip[7]) && low(ip[8]) && low(ip[9]) if len(ip) == IPv6len && isZeros(ip[0:10]) && ip[10] == 255 && From e475136d705e00db68c1180f237d2dcf58be34b3 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 00:18:54 +0200 Subject: [PATCH 076/104] Minor adjustments --- pkg/slayers/path/hopfield_spec.gobra | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index e4de2bf76..c2de1d8d7 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -115,8 +115,8 @@ pure func (h HopField) Abs() (io.HF) { HVF: AbsMac(h.Mac), } } -// TODO: Once Gobra issue 883 is resolved, these could be removed in favor of -// unfolding permissions directly in the contract. +// NOTE[henri]: Once Gobra issue 883 is resolved, these could be removed in +// favor of unfolding permissions directly in the contract. // - We might prefer such getter functions anyway, though. // - Alternatively, we could also have a "`Dereference`" function returning // the underlying `HopField` which we could then access. From e59f604b003347e7476f4f5acbcd415a1ece1f8d Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 11:45:09 +0200 Subject: [PATCH 077/104] Clean up --- pkg/slayers/path/epic/epic.go | 1 - pkg/slayers/path/epic/epic_spec.gobra | 10 ++-------- pkg/slayers/path/path.go | 18 +++++------------- pkg/slayers/path/path_spec.gobra | 2 +- pkg/slayers/path/scion/decoded_spec.gobra | 2 +- pkg/slayers/path/scion/raw.go | 2 +- pkg/slayers/path/scion/raw_spec.gobra | 2 +- 7 files changed, 11 insertions(+), 26 deletions(-) diff --git a/pkg/slayers/path/epic/epic.go b/pkg/slayers/path/epic/epic.go index 1d165d33f..1fa1c5307 100644 --- a/pkg/slayers/path/epic/epic.go +++ b/pkg/slayers/path/epic/epic.go @@ -91,7 +91,6 @@ type Path struct { // @ ensures old(p.getLHVFLen(ubuf)) != HVFLen ==> r != nil // @ decreases func (p *Path) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { - // p.GetLowSerializeTo(ubuf, R1/2) //@ p.RevealIsLow(ubuf) if len(b) < p.Len( /*@ ubuf @*/ ) { return serrors.New("buffer too small to serialize path.", "expected", int(p.Len( /*@ ubuf @*/ )), diff --git a/pkg/slayers/path/epic/epic_spec.gobra b/pkg/slayers/path/epic/epic_spec.gobra index 3d7c8df5c..0f3742e68 100644 --- a/pkg/slayers/path/epic/epic_spec.gobra +++ b/pkg/slayers/path/epic/epic_spec.gobra @@ -104,20 +104,14 @@ pure func (p *Path) IsValidResultOfDecoding(b []byte) (res bool) { (*Path) implements path.Path -// pred (*Path) LowSerializeTo() +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted requires p.Mem(ub) decreases pure func (p *Path) IsLow(ub []byte) bool -// ghost -// requires ep.LowSerializeTo() && p > 0 -// preserves acc(ep.Mem(buf), p) -// ensures low(ep.getPHVFLen(buf) != HVFLen) && low(ep.getLHVFLen(buf) != HVFLen) -// decreases -// func (ep *Path) GetLowSerializeTo(buf []byte, ghost p perm) - +// TODO: Once Gobra issue #846 is resolved, actually implement this. ghost trusted requires acc(p.Mem(ub), R2) && p.IsLow(ub) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 3e23d6392..58ef05328 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -74,23 +74,15 @@ type Path interface { // (VerifiedSCION) Must imply the resources required to initialize // a new instance of a predicate. //@ pred NonInitMem() - // SerializeTo serializes the path into the provided buffer. - // (VerifiedSCION) There are implementations of this interface that modify the underlying - // structure when serializing (e.g. scion.Raw) - // TODO: Once Gobra issue 846 is resolved, rework this. - // Predicate containing sensitivity information. Intended to be implemented - // as an abstract predicate, alongside an abstract function requiring - // `LowSerializeTo()`, and ensuring the corresponding sensitivity. - // pred LowSerializeTo() - // TODO: add documentation/comment - // TODO: does it make sense to have separate IsLow* for each method here? - // TODO: at least in the future might need to add a parameter to this to - // distinguish between NonInitMem and Mem + //@ ghost //@ requires Mem(ub) //@ decreases //@ pure IsLow(ghost ub []byte) bool - // requires LowSerializeTo() + + // SerializeTo serializes the path into the provided buffer. + // (VerifiedSCION) There are implementations of this interface that modify the underlying + // structure when serializing (e.g. scion.Raw) //@ requires low(len(b)) //@ requires acc(Mem(ub), R1) //@ requires IsLow(ub) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index 72f59d7b7..df44b2928 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -31,7 +31,7 @@ pred (r *rawPath) NonInitMem() { acc(r) } -// pred (*rawPath) LowSerializeTo() +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted requires r.Mem(ub) diff --git a/pkg/slayers/path/scion/decoded_spec.gobra b/pkg/slayers/path/scion/decoded_spec.gobra index df21d2233..fe9c9fbb7 100644 --- a/pkg/slayers/path/scion/decoded_spec.gobra +++ b/pkg/slayers/path/scion/decoded_spec.gobra @@ -47,7 +47,7 @@ pred (d *Decoded) Mem(ubuf []byte) { (forall i int :: { &d.HopFields[i] } 0 <= i && i < len(d.HopFields) ==> d.HopFields[i].Mem()) } -// pred (*Decoded) LowSerializeTo() +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted requires d.Mem(ub) diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index 821f5174b..b8d869f26 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -41,7 +41,7 @@ type Raw struct { // @ s.GetBase(data).WeaklyValid() && // @ s.GetBase(data).EqAbsHeader(data) // @ ensures res != nil ==> (s.NonInitMem() && res.ErrorMem()) -// @ ensures low(res == nil) +// @ ensures low(res == nil) // @ decreases func (s *Raw) DecodeFromBytes(data []byte) (res error) { //@ unfold s.NonInitMem() diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index 1d8436068..fda662ce9 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -38,7 +38,7 @@ pred (s *Raw) Mem(buf []byte) { len(s.Raw) == s.Base.Len() } -// pred (s *Raw) LowSerializeTo() +// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost trusted requires s.Mem(ub) From b8d8b827681c2b966b5bdc7086d717802517b679 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 11:50:59 +0200 Subject: [PATCH 078/104] Make pkg/slayers/path/empty verify again --- pkg/slayers/path/empty/empty_spec.gobra | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/slayers/path/empty/empty_spec.gobra b/pkg/slayers/path/empty/empty_spec.gobra index a4bd86d16..7aa9e07b5 100644 --- a/pkg/slayers/path/empty/empty_spec.gobra +++ b/pkg/slayers/path/empty/empty_spec.gobra @@ -46,6 +46,13 @@ pure func (p Path) LenSpec(ghost ub []byte) (l int) { return PathLen } +ghost +requires p.Mem(ub) +decreases +pure func (p Path) IsLow(ub []byte) bool { + return true +} + Path implements path.Path // Definitions to allow *Path to be treated as a path.Path From 06a49074d698bd6299987c81e79600cf26df3131 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 12:39:58 +0200 Subject: [PATCH 079/104] Further clean up --- pkg/slayers/path/epic/epic.go | 2 +- pkg/slayers/path/epic/epic_spec.gobra | 2 +- pkg/slayers/path/path_spec.gobra | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/slayers/path/epic/epic.go b/pkg/slayers/path/epic/epic.go index 1fa1c5307..d71979af5 100644 --- a/pkg/slayers/path/epic/epic.go +++ b/pkg/slayers/path/epic/epic.go @@ -140,7 +140,7 @@ func (p *Path) SerializeTo(b []byte /*@, ghost ubuf []byte @*/) (r error) { // DecodeFromBytes deserializes the buffer b into the Path. On failure, an error is returned, // otherwise SerializeTo will return nil. // @ requires p.NonInitMem() -// @ requires low(len(b) < MetadataLen) +// @ requires low(len(b)) // @ preserves acc(sl.Bytes(b, 0, len(b)), R42) // @ ensures len(b) < MetadataLen ==> r != nil // @ ensures r == nil ==> p.Mem(b) diff --git a/pkg/slayers/path/epic/epic_spec.gobra b/pkg/slayers/path/epic/epic_spec.gobra index 0f3742e68..cf4b768fe 100644 --- a/pkg/slayers/path/epic/epic_spec.gobra +++ b/pkg/slayers/path/epic/epic_spec.gobra @@ -116,6 +116,6 @@ ghost trusted requires acc(p.Mem(ub), R2) && p.IsLow(ub) ensures acc(p.Mem(ub), R2) -ensures low(p.getPHVFLen(ub) != HVFLen) && low(p.getLHVFLen(ub) != HVFLen) +ensures low(p.getPHVFLen(ub)) && low(p.getLHVFLen(ub)) decreases func (p *Path) RevealIsLow(ub []byte) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index df44b2928..f96144402 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -31,12 +31,12 @@ pred (r *rawPath) NonInitMem() { acc(r) } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. ghost -trusted requires r.Mem(ub) decreases -pure func (r *rawPath) IsLow(ub []byte) bool +pure func (r *rawPath) IsLow(ub []byte) bool { + return true +} ghost requires p.Mem(buf) From 886fbe530843e4fbbce61559e8e241f795b0f812 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 13:08:32 +0200 Subject: [PATCH 080/104] Add explicit contract to `TrimSuffix` --- verification/dependencies/strings/strings.gobra | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index e2d645042..cc9d567b0 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -159,8 +159,9 @@ func TrimPrefix(s, prefix string) string // TrimSuffix returns s without the provided trailing suffix string. // If s doesn't end with suffix, s is returned unchanged. +ensures low(s) && low(suffix) ==> low(res) decreases _ -pure func TrimSuffix(s, suffix string) string +func TrimSuffix(s, suffix string) (res string) // Replace returns a copy of the string s with the first n // non-overlapping instances of old replaced by new. From 315a6531a13802e359dda411fdc1c68e376a0e9a Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 13:19:16 +0200 Subject: [PATCH 081/104] Adapt pure annotations elsewhere --- pkg/addr/host.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 916fc1aca..625fe9dfb 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -389,15 +389,15 @@ func (h HostSVC) IP() (res net.IP) { return nil } +// @ ensures low(h) ==> low(res) // @ decreases -// @ pure -func (h HostSVC) IsMulticast() bool { +func (h HostSVC) IsMulticast() (res bool) { return (h & SVCMcast) != 0 } +// @ ensures low(h) ==> low(res) // @ decreases -// @ pure -func (h HostSVC) Base() HostSVC { +func (h HostSVC) Base() (res HostSVC) { return h & ^SVCMcast } From d14eb1e57ee749435888a78fe4c1d5d5df60a4be Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 6 Aug 2025 13:52:45 +0200 Subject: [PATCH 082/104] Adapt pure annotations that I missed --- pkg/addr/isdas.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index 0be19b0a7..b42a090a0 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -235,15 +235,15 @@ func ParseIA(ia string) (retIA IA, retErr error) { return MustIAFrom(isd, _as), nil } +// @ ensures low(ia) ==> low(res) // @ decreases -// @ pure -func (ia IA) ISD() ISD { +func (ia IA) ISD() (res ISD) { return ISD(ia >> ASBits) } +// @ ensures low(ia) ==> low(res) // @ decreases -// @ pure -func (ia IA) AS() AS { +func (ia IA) AS() (res AS) { return AS(ia) & MaxAS } From ceacce81d45219d2a80efe0555da527bed6818de Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 13:57:51 +0200 Subject: [PATCH 083/104] Address PR feedback and add `&& low(i) ==>` --- private/topology/linktype.go | 31 ++++++------------------ verification/utils/sif/assumptions.gobra | 2 +- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 836d0df45..81f99e6a1 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -62,13 +62,12 @@ func (l LinkType) String() string { // LinkTypeFromString returns the numerical link type associated with a string description. If the // string is not recognized, an Unset link type is returned. The matching is case-insensitive. // @ requires low(s) -// @ ensures low(res) // @ decreases func LinkTypeFromString(s string) (res LinkType) { var l /*@@@*/ LinkType tmp := []byte(s) // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { &tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) + //@ assume forall i int :: { &tmp[i] } 0 <= i && i < len(tmp) && low(i) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) if err := l.UnmarshalText(tmp); err != nil { return Unset @@ -79,36 +78,24 @@ func LinkTypeFromString(s string) (res LinkType) { // @ requires low(l) // @ ensures (l == Core || l == Parent || l == Child || l == Peer) == (err == nil) // @ ensures err == nil ==> sl.Bytes(res, 0, len(res)) -// @ ensures err == nil ==> low(len(res)) && -// @ forall i int :: { sl.GetByte(res, 0, len(res), i) } 0 <= i && i < len(res) ==> -// @ low(sl.GetByte(res, 0, len(res), i)) // @ ensures err != nil ==> err.ErrorMem() -// @ ensures low(err != nil) // @ decreases func (l LinkType) MarshalText() (res []byte, err error) { switch l { case Core: tmp := []byte("core") - // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Parent: tmp := []byte("parent") - // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Child: tmp := []byte("child") - // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil case Peer: tmp := []byte("peer") - // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { tmp[i] } 0 <= i && i < len(tmp) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) return tmp, nil default: @@ -118,21 +105,19 @@ func (l LinkType) MarshalText() (res []byte, err error) { // @ requires acc(sl.Bytes(data, 0, len(data)), R15) // @ requires low(len(data)) && -// @ forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) ==> -// @ low(sl.GetByte(data, 0, len(data), i)) +// @ forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) && low(i) ==> +// @ low(sl.GetByte(data, 0, len(data), i)) // @ preserves acc(l) // @ ensures acc(sl.Bytes(data, 0, len(data)), R15) // @ ensures err != nil ==> err.ErrorMem() -// @ ensures err == nil ==> low(*l) // @ ensures low(err != nil) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { - //@ BeforeUnfold: - //@ unfold acc(sl.Bytes(data, 0, len(data)), R15) - //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R15) - //@ assert forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) ==> - //@ old[BeforeUnfold](sl.GetByte(data, 0, len(data), i)) == data[i] - //@ sif.LowSliceImpliesLowString(data, R15) + //@ unfold acc(sl.Bytes(data, 0, len(data)), R16) + //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R16) + //@ assert forall i int :: { &data[i] } 0 <= i && i < len(data) && low(i) ==> + //@ sl.GetByte(data, 0, len(data), i) == data[i] + //@ sif.LowSliceImpliesLowString(data, R16) switch strings.ToLower(string(data)) { case "core": *l = Core diff --git a/verification/utils/sif/assumptions.gobra b/verification/utils/sif/assumptions.gobra index 8a612bee3..2afbf9fed 100644 --- a/verification/utils/sif/assumptions.gobra +++ b/verification/utils/sif/assumptions.gobra @@ -5,7 +5,7 @@ package sif // TODO: Once Gobra issue #832 is resolved, introduce body and prove this. ghost requires p > 0 && acc(b, p) -requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) ==> low(b[i]) +requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) && low(i) ==> low(b[i]) ensures acc(b, p) && low(string(b)) decreases func LowSliceImpliesLowString(b []byte, p perm) From 9686422c67fc2e0094ac6bc060026e678a3c3a8e Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 21:51:09 +0200 Subject: [PATCH 084/104] Make contract of `net.IP.To4` less implementation-specific --- pkg/addr/host.go | 41 +++++++++++++++----------- pkg/addr/host_spec.gobra | 26 +++++++++++++--- verification/dependencies/net/ip.gobra | 5 ++-- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 625fe9dfb..29dafb299 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -92,6 +92,14 @@ const ( type HostAddr interface { //@ pred Mem() + // NOTE[henri]: I rewrote this comment to be more accurate + // Return whether all the underlying data that needs to be low for the + // computation of `Pack`, `IP`, `String` is low. + //@ ghost + //@ requires Mem() + //@ decreases + //@ pure IsLow() bool + //@ preserves acc(Mem(), R13) //@ decreases Size() int @@ -100,12 +108,12 @@ type HostAddr interface { //@ decreases Type() HostAddrType - //@ requires acc(Mem(), R13) + //@ requires acc(Mem(), R13) && IsLow() //@ ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i], R13) //@ decreases Pack() (res []byte) - //@ requires acc(Mem(), R13) + //@ requires acc(Mem(), R13) && IsLow() //@ ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i], R13) //@ decreases IP() (res net.IP) @@ -125,14 +133,6 @@ type HostAddr interface { // replaced by the String() method which is the one that should be implemented //fmt.Stringer - // NOTE[henri]: I rewrote this comment to be more accurate - // Return whether all the underlying data that needs to be low for the - // computation of `String` is low. - //@ ghost - //@ requires Mem() - //@ decreases - //@ pure IsLow() bool - //@ requires acc(Mem(), R13) && IsLow() //@ ensures acc(Mem(), R13) //@ decreases @@ -202,21 +202,28 @@ func (h HostIPv4) Type() HostAddrType { return HostTypeIPv4 } -// @ requires acc(h.Mem(), R13) +// @ requires acc(h.Mem(), R13) && h.IsLow() // @ ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> acc(&res[i], R13) // @ decreases func (h HostIPv4) Pack() (res []byte) { return []byte(h.IP()) } -// @ requires acc(h.Mem(), R13) +// @ requires acc(h.Mem(), R13) && h.IsLow() // @ ensures forall i int :: { &res[i] }{ &h[i] } 0 <= i && i < len(res) ==> acc(&res[i], R13) && &res[i] == &h[i] // @ ensures len(res) == HostLenIPv4 // @ decreases func (h HostIPv4) IP() (res net.IP) { // XXX(kormat): ensure the reply is the 4-byte representation. - //@ unfold acc(h.Mem(), R13) - //@ unfold acc(sl.Bytes(h, 0, len(h)), R13) + //@ h.RevealIsLow(R13) + //@ unfold acc(h.Mem(), R13/2) + //@ assert forall i int :: { sl.GetByte(h, 0, len(h), i) }{ h.GetByte(i) } 0 <= i && i < len(h) ==> + //@ sl.GetByte(h, 0, len(h), i) == h.GetByte(i) + //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) + //@ assert forall i int :: { h.GetByte(i) }{ &h[i] } 0 <= i && i < len(h) ==> + //@ h.GetByte(i) == h[i] + //@ unfold acc(h.Mem(), R13/2) + //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) return net.IP(h).To4( /*@ false @*/ ) } @@ -247,7 +254,7 @@ func (h HostIPv4) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ preserves acc(h.Mem(), R13) +// @ preserves acc(h.Mem(), R13) && h.IsLow() // @ decreases func (h HostIPv4) String() string { //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 @@ -499,8 +506,8 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { // @ requires acc(ip) // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 -// @ requires low(len(ip)) && (len(ip) == HostLenIPv6 ==> -// @ forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i])) +// @ requires low(len(ip)) && +// @ forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i]) // @ ensures res.Mem() // @ decreases func HostFromIP(ip net.IP) (res HostAddr) { diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index 8fb574997..ce42e8726 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -41,15 +41,33 @@ pred (h HostIPv4) Mem() { slices.Bytes(h, 0, len(h)) } -// NOTE[henri]: As `HostIPv4.String` doesn't need anything to be low, I -// implemented it trivially already. ghost requires h.Mem() +requires 0 <= i && i < len(h) decreases -pure func (h HostIPv4) IsLow() bool { - return true +pure func (h HostIPv4) GetByte(i int) byte { + return unfolding h.Mem() in slices.GetByte(h, 0, len(h), i) } +// TODO: Once Gobra issue #846 is resolved, implement this. +ghost +trusted +requires h.Mem() +decreases +pure func (h HostIPv4) IsLow() bool + +// "Reveal the body" of `h.IsLow` (necessary bc. we can't implement `h.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. +ghost +trusted +requires p > 0 +requires acc(h.Mem(), p) && h.IsLow() +ensures acc(h.Mem(), p) +ensures low(len(h)) && + forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) ==> low(h.GetByte(i)) +decreases +func (h HostIPv4) RevealIsLow(p perm) + HostIPv4 implements HostAddr pred (h HostIPv6) Mem() { diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 5a958864a..01e75e38e 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -63,8 +63,7 @@ func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) requires !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) -requires low(len(ip)) && (len(ip) == IPv6len ==> - forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i])) +requires low(len(ip)) && forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i]) ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) ensures !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len @@ -84,7 +83,7 @@ func (ip IP) To4(ghost wildcard bool) (res IP) { if len(ip) == IPv4len { return ip } - // This assertion is ecessary to infer `low(isZeros(ip[0:10]))`. + // This assertion is necessary to infer `low(isZeros(ip[0:10]))`. assert len(ip) == IPv6len ==> low(ip[0]) && low(ip[1]) && low(ip[2]) && low(ip[3]) && low(ip[4]) && low(ip[5]) && low(ip[6]) && low(ip[7]) && low(ip[8]) && low(ip[9]) From e2701801444112d862bb2a3ca38d57b8c857b05f Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 22:22:19 +0200 Subject: [PATCH 085/104] Add `&& low(i) ==>` in quantified assertions where appropriate --- pkg/addr/host.go | 16 ++++++++-------- pkg/addr/host_spec.gobra | 4 ++-- pkg/addr/isdas.go | 11 ++++++----- .../dependencies/encoding/encoding.gobra | 4 ++-- verification/dependencies/net/ip.gobra | 5 +++-- verification/dependencies/strings/strings.gobra | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 29dafb299..6ce93eb8f 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -217,11 +217,11 @@ func (h HostIPv4) IP() (res net.IP) { // XXX(kormat): ensure the reply is the 4-byte representation. //@ h.RevealIsLow(R13) //@ unfold acc(h.Mem(), R13/2) - //@ assert forall i int :: { sl.GetByte(h, 0, len(h), i) }{ h.GetByte(i) } 0 <= i && i < len(h) ==> - //@ sl.GetByte(h, 0, len(h), i) == h.GetByte(i) + //@ assert forall i int :: { sl.GetByte(h, 0, len(h), i) }{ h.GetByte(i) } 0 <= i && i < len(h) && + //@ low(i) ==> sl.GetByte(h, 0, len(h), i) == h.GetByte(i) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) - //@ assert forall i int :: { h.GetByte(i) }{ &h[i] } 0 <= i && i < len(h) ==> - //@ h.GetByte(i) == h[i] + //@ assert forall i int :: { h.GetByte(i) }{ &h[i] } 0 <= i && i < len(h) && + //@ low(i) ==> h.GetByte(i) == h[i] //@ unfold acc(h.Mem(), R13/2) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) return net.IP(h).To4( /*@ false @*/ ) @@ -254,7 +254,8 @@ func (h HostIPv4) Equal(o HostAddr) bool { return ok && net.IP(h).Equal(net.IP(ha)) } -// @ preserves acc(h.Mem(), R13) && h.IsLow() +// @ requires acc(h.Mem(), R13) && h.IsLow() +// @ ensures acc(h.Mem(), R13) // @ decreases func (h HostIPv4) String() string { //@ assert unfolding acc(h.Mem(), R13) in len(h) == HostLenIPv4 @@ -506,8 +507,8 @@ func HostFromRaw(b []byte, htype HostAddrType) (res HostAddr, err error) { // @ requires acc(ip) // @ requires len(ip) == HostLenIPv4 || len(ip) == HostLenIPv6 -// @ requires low(len(ip)) && -// @ forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i]) +// @ requires low(len(ip)) && forall i int :: { &ip[i] } 0 <= i && i < len(ip) && +// @ low(i) ==> low(ip[i]) // @ ensures res.Mem() // @ decreases func HostFromIP(ip net.IP) (res HostAddr) { @@ -533,7 +534,6 @@ func HostFromIPStr(s string) (res HostAddr) { //@ fold tmp.Mem() return tmp } - //@ assert len(ip) > 0 ==> low(ip[0]) return HostFromIP(ip) } diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index ce42e8726..ed9dfcc11 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -63,8 +63,8 @@ trusted requires p > 0 requires acc(h.Mem(), p) && h.IsLow() ensures acc(h.Mem(), p) -ensures low(len(h)) && - forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) ==> low(h.GetByte(i)) +ensures low(len(h)) && forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) && + low(i) ==> low(h.GetByte(i)) decreases func (h HostIPv4) RevealIsLow(p perm) diff --git a/pkg/addr/isdas.go b/pkg/addr/isdas.go index b42a090a0..8f536a334 100644 --- a/pkg/addr/isdas.go +++ b/pkg/addr/isdas.go @@ -99,7 +99,8 @@ func parseAS(_as string, sep string) (retAs AS, retErr error) { var parsed AS //@ invariant 0 <= i && i <= asParts //@ invariant acc(parts) - //@ invariant forall i int :: { parts[i] } 0 <= i && i < len(parts) ==> low(parts[i]) + //@ invariant forall i int :: { parts[i] } 0 <= i && i < len(parts) && + //@ low(i) ==> low(parts[i]) //@ invariant low(i) && low(_as) && low(parsed) //@ decreases asParts - i for i := 0; i < asParts; i++ { @@ -166,8 +167,8 @@ func (_as AS) MarshalText() ([]byte, error) { } // @ requires forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) -// @ requires low(len(text)) && -// @ forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) +// @ requires low(len(text)) && forall i int :: { text[i] } 0 <= i && i < len(text) && +// @ low(i) ==> low(text[i]) // @ preserves acc(_as) // @ ensures forall i int :: { &text[i] } 0 <= i && i < len(text) ==> acc(&text[i]) // @ decreases @@ -253,8 +254,8 @@ func (ia IA) MarshalText() ([]byte, error) { } // @ requires forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) -// @ requires low(len(b)) && -// @ forall i int :: { b[i] } 0 <= i && i < len(b) ==> low(b[i]) +// @ requires low(len(b)) && forall i int :: { b[i] } 0 <= i && i < len(b) && +// @ low(i) ==> low(b[i]) // @ preserves acc(ia) // @ ensures forall i int :: { &b[i] } 0 <= i && i < len(b) ==> acc(&b[i]) // @ decreases diff --git a/verification/dependencies/encoding/encoding.gobra b/verification/dependencies/encoding/encoding.gobra index e801cd559..9818da569 100644 --- a/verification/dependencies/encoding/encoding.gobra +++ b/verification/dependencies/encoding/encoding.gobra @@ -20,8 +20,8 @@ type TextUnmarshaler interface { pred Mem() requires acc(text) - requires low(len(text)) && - forall i int :: { text[i] } 0 <= i && i < len(text) ==> low(text[i]) + requires low(len(text)) && forall i int :: { text[i] } 0 <= i && i < len(text) && + low(i) ==> low(text[i]) preserves Mem() ensures acc(text) decreases diff --git a/verification/dependencies/net/ip.gobra b/verification/dependencies/net/ip.gobra index 01e75e38e..bfac564a7 100644 --- a/verification/dependencies/net/ip.gobra +++ b/verification/dependencies/net/ip.gobra @@ -63,7 +63,8 @@ func (ip IP) IsGlobalUnicast() bool // To4 converts the IPv4 address ip to a 4-byte representation. requires wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) requires !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) -requires low(len(ip)) && forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> low(ip[i]) +requires low(len(ip)) && forall i int :: { &ip[i] } 0 <= i && i < len(ip) && + low(i) ==> low(ip[i]) ensures wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], _) ensures !wildcard ==> forall i int :: { &ip[i] } 0 <= i && i < len(ip) ==> acc(&ip[i], R20) ensures res != nil ==> len(res) == IPv4len @@ -161,6 +162,6 @@ pure func (ip IP) Equal(x IP) bool ensures forall i int :: {&res[i]} 0 <= i && i < len(res) ==> acc(&res[i]) ensures res != nil ==> len(res) == IPv4len || len(res) == IPv6len ensures low(s) ==> low(res == nil) && low(len(res)) && - forall i int :: { &res[i] } 0 <= i && i < len(res) ==> low(res[i]) + forall i int :: { &res[i] } 0 <= i && i < len(res) && low(i) ==> low(res[i]) decreases _ func ParseIP(s string) (res IP) diff --git a/verification/dependencies/strings/strings.gobra b/verification/dependencies/strings/strings.gobra index cc9d567b0..68324344a 100644 --- a/verification/dependencies/strings/strings.gobra +++ b/verification/dependencies/strings/strings.gobra @@ -67,7 +67,7 @@ func SplitAfterN(s, sep string, n int) (res []string) // Split slices s into all substrings separated by sep and returns a slice of // the substrings between those separators. ensures forall i int :: { &res[i] } 0 <= i && i < len(res) ==> - acc(&res[i]) && ((low(s) && low(sep)) ==> low(res[i])) + acc(&res[i]) && ((low(s) && low(sep)) ==> low(i) ==> low(res[i])) ensures (low(s) && low(sep)) ==> low(len(res)) decreases _ func Split(s, sep string) (res []string) //{ return genSplit(s, sep, 0, -1) } From 92a35f7db8f18415dc6fd453ec1fee4eef4d7ec9 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 22:36:32 +0200 Subject: [PATCH 086/104] Clean up --- pkg/addr/host.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 6ce93eb8f..0a1eafa1f 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -217,10 +217,10 @@ func (h HostIPv4) IP() (res net.IP) { // XXX(kormat): ensure the reply is the 4-byte representation. //@ h.RevealIsLow(R13) //@ unfold acc(h.Mem(), R13/2) - //@ assert forall i int :: { sl.GetByte(h, 0, len(h), i) }{ h.GetByte(i) } 0 <= i && i < len(h) && + //@ assert forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) && //@ low(i) ==> sl.GetByte(h, 0, len(h), i) == h.GetByte(i) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) - //@ assert forall i int :: { h.GetByte(i) }{ &h[i] } 0 <= i && i < len(h) && + //@ assert forall i int :: { &h[i] } 0 <= i && i < len(h) && //@ low(i) ==> h.GetByte(i) == h[i] //@ unfold acc(h.Mem(), R13/2) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) From 0003f81cefc75a9a7c2d2e5af426b355d7e3ae94 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 23:05:42 +0200 Subject: [PATCH 087/104] Remove `&& low(i) ==>` from one assertion not dealing with sensitivity --- private/topology/linktype.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 81f99e6a1..568793f00 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -115,7 +115,7 @@ func (l LinkType) MarshalText() (res []byte, err error) { func (l *LinkType) UnmarshalText(data []byte) (err error) { //@ unfold acc(sl.Bytes(data, 0, len(data)), R16) //@ ghost defer fold acc(sl.Bytes(data, 0, len(data)), R16) - //@ assert forall i int :: { &data[i] } 0 <= i && i < len(data) && low(i) ==> + //@ assert forall i int :: { &data[i] } 0 <= i && i < len(data) ==> //@ sl.GetByte(data, 0, len(data), i) == data[i] //@ sif.LowSliceImpliesLowString(data, R16) switch strings.ToLower(string(data)) { From 09e4f2f809bceac52f69d2f711bf1fc10fa40ee1 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 12 Aug 2025 23:12:00 +0200 Subject: [PATCH 088/104] Remove occurrences of `&& low(i) ==>` added accidentally --- pkg/addr/host.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 0a1eafa1f..633789544 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -217,11 +217,11 @@ func (h HostIPv4) IP() (res net.IP) { // XXX(kormat): ensure the reply is the 4-byte representation. //@ h.RevealIsLow(R13) //@ unfold acc(h.Mem(), R13/2) - //@ assert forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) && - //@ low(i) ==> sl.GetByte(h, 0, len(h), i) == h.GetByte(i) + //@ assert forall i int :: { h.GetByte(i) } 0 <= i && i < len(h) ==> + //@ sl.GetByte(h, 0, len(h), i) == h.GetByte(i) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) - //@ assert forall i int :: { &h[i] } 0 <= i && i < len(h) && - //@ low(i) ==> h.GetByte(i) == h[i] + //@ assert forall i int :: { &h[i] } 0 <= i && i < len(h) ==> + //@ h.GetByte(i) == h[i] //@ unfold acc(h.Mem(), R13/2) //@ unfold acc(sl.Bytes(h, 0, len(h)), R13/2) return net.IP(h).To4( /*@ false @*/ ) From 5ed0d5ba3d409182da931e867b7b4110af49439a Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 13 Aug 2025 12:55:36 +0200 Subject: [PATCH 089/104] Address PR feedback --- private/underlay/conn/conn.go | 14 +++----------- private/underlay/conn/conn_spec.gobra | 8 ++------ router/dataplane.go | 3 +++ verification/dependencies/net/udpsock.gobra | 4 ---- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 2df9dc301..b706c4dff 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -44,6 +44,8 @@ type Messages []ipv4.Message type Conn interface { //@ pred Mem() + // Return whether all the underlying data that needs to be low for the + // computation of `WriteTo`, `Close` is low. //@ ghost //@ requires Mem() //@ decreases @@ -488,17 +490,7 @@ func NewReadMessages(n int) (res Messages) { m := make(Messages, n) //@ invariant forall j int :: { &m[j] } (0 <= j && j < i0) ==> m[j].Mem() //@ invariant forall j int :: { m[j].GetAddr() } (0 <= j && j < i0) ==> m[j].GetAddr() == nil - // NOTE[henri]: Splitting up the following invariant has only marginally - // improved stability (if at all), thus we could consider undoing this. - // invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) - //@ invariant forall j int :: { &m[j].Buffers } (i0 <= j && j < len(m)) ==> acc(&m[j].Buffers) - //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> acc(&m[j].OOB) - //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> acc(&m[j].Addr) - //@ invariant forall j int :: { &m[j].N } (i0 <= j && j < len(m)) ==> acc(&m[j].N) - //@ invariant forall j int :: { &m[j].NN } (i0 <= j && j < len(m)) ==> acc(&m[j].NN) - //@ invariant forall j int :: { &m[j].Flags } (i0 <= j && j < len(m)) ==> acc(&m[j].Flags) - //@ invariant forall j int :: { &m[j].IsActive } (i0 <= j && j < len(m)) ==> acc(&m[j].IsActive) - //@ invariant forall j int :: { &m[j].WildcardPerm } (i0 <= j && j < len(m)) ==> acc(&m[j].WildcardPerm) + //@ invariant forall j int :: { &m[j] } (i0 <= j && j < len(m)) ==> acc(&m[j]) //@ invariant forall j int :: { &m[j].N } (i0 <= j && j < len(m)) ==> m[j].N == 0 //@ invariant forall j int :: { &m[j].Addr } (i0 <= j && j < len(m)) ==> m[j].Addr == nil //@ invariant forall j int :: { &m[j].OOB } (i0 <= j && j < len(m)) ==> m[j].OOB == nil diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index 354018783..04c832909 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -40,10 +40,6 @@ pred (c *connUDPBase) MemWithoutConn() { (c.Remote != nil ==> acc(c.Remote.Mem(), _)) } -// NOTE[henri]: Once Gobra issue #883 is resolved, we could remove `GetClosed` -// etc. in favor of unfolding `Mem` in the contract of `RevealIsLow` etc. -// directly. However, I'm not sure whether that is preferrable. If it is, add -// a corresponding TODO here. ghost requires withConn ==> c.Mem() requires !withConn ==> c.MemWithoutConn() @@ -165,7 +161,7 @@ pred (c *Config) Mem() { requires c.Mem() decreases -pure func (c *Config) Dereference() Config { +pure func (c *Config) Deref() Config { return unfolding c.Mem() in *c } @@ -181,7 +177,7 @@ pure func (c *Config) IsLow() bool ghost trusted requires acc(c.Mem(), R1) && c.IsLow() -ensures acc(c.Mem(), R1) && low(c.Dereference()) +ensures acc(c.Mem(), R1) && low(c.Deref()) decreases func (c *Config) RevealIsLow() diff --git a/router/dataplane.go b/router/dataplane.go index d524151e0..b2f4a5267 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -149,6 +149,9 @@ type bfdSession interface { type BatchConn interface { // @ pred Mem() + // Return whether all the underlying data that needs to be low for the + // computation of `WriteTo`, `Close` is low. + // We add this here to match the `Conn` interface in `private/underlay/conn`. // @ ghost // @ requires Mem() // @ decreases diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index e3e0f01af..cf6022ee4 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -27,10 +27,6 @@ pred (a *UDPAddr) Mem() { acc(a, R5) && sl.Bytes(a.IP, 0, len(a.IP)) } -// NOTE[henri]: Once Gobra issue #883 is resolved, we could remove `GetIP` -// etc. in favor of unfolding `Mem` in the contract of `RevealIsLow` directly. -// However, I'm not sure whether that is preferrable. If it is, add a -// corresponding TODO here. requires a.Mem() decreases pure func (a *UDPAddr) GetIP() IP { From 2bd4ffd32296fabc95c3d14c9fc4fbfa7866cab0 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 13 Aug 2025 13:09:37 +0200 Subject: [PATCH 090/104] Add `&& low(i) ==>` --- verification/dependencies/net/udpsock.gobra | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index cf6022ee4..ee0473a6a 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -57,7 +57,7 @@ requires acc(a.Mem(), R15) && a.IsLow() ensures acc(a.Mem(), R15) ensures low(len(a.GetIP())) ensures forall i int :: { a.GetIPByte(i) } 0 <= i && i < len(a.GetIP()) ==> - low(a.GetIPByte(i)) + low(i) ==> low(a.GetIPByte(i)) decreases func (a *UDPAddr) RevealIsLow() From f24f0b32d4687160049b6c71c7ee0a58ba0715b4 Mon Sep 17 00:00:00 2001 From: henriman Date: Wed, 13 Aug 2025 16:51:24 +0200 Subject: [PATCH 091/104] Refer to Gobra issue #957 on NewReadMessages --- private/underlay/conn/conn.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index b706c4dff..95f94b70c 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -477,9 +477,8 @@ func (c *connUDPBase) Close() (err error) { // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. -// NOTE[henri]: The verification of this function is still somewhat unstable, -// although adjusting the triggers made it significantly more stable and it now -// verifies successfully the majority of the time. I'm not sure how to improve +// NOTE: The verification of this function is somewhat unstable. +// See Gobra issue #957. // this further. // @ requires 0 < n // @ requires low(n) From 27f66a534053aa14ffc391f213c5a3fbc8a85bb0 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 16:45:07 +0200 Subject: [PATCH 092/104] Remove assumption for Gobra issue 831 Issue 831 is fixed by PR 961 --- private/topology/linktype.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 568793f00..5bbd0c9d0 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -66,8 +66,6 @@ func (l LinkType) String() string { func LinkTypeFromString(s string) (res LinkType) { var l /*@@@*/ LinkType tmp := []byte(s) - // TODO: Once Gobra issue #831 is resolved, remove this assumption. - //@ assume forall i int :: { &tmp[i] } 0 <= i && i < len(tmp) && low(i) ==> low(tmp[i]) //@ fold sl.Bytes(tmp, 0, len(tmp)) if err := l.UnmarshalText(tmp); err != nil { return Unset From 555f91acf81dfde9171fe4be08fcdd1c7c899eee Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 19:24:52 +0200 Subject: [PATCH 093/104] Add TODO to IsLow regarding adding hyper --- pkg/addr/host.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index 633789544..f795ca602 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -95,6 +95,7 @@ type HostAddr interface { // NOTE[henri]: I rewrote this comment to be more accurate // Return whether all the underlying data that needs to be low for the // computation of `Pack`, `IP`, `String` is low. + // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. //@ ghost //@ requires Mem() //@ decreases From 000a7d90616621938dd7fa8b62813486a2126045 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 19:28:24 +0200 Subject: [PATCH 094/104] Add TODO to IsLow regarding adding hyper --- private/underlay/conn/conn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 95f94b70c..396d3f6f0 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -46,6 +46,7 @@ type Conn interface { // Return whether all the underlying data that needs to be low for the // computation of `WriteTo`, `Close` is low. + // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. //@ ghost //@ requires Mem() //@ decreases From 296be69e77ded829307947f81e2bb90147bafc4b Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 19:38:56 +0200 Subject: [PATCH 095/104] Add TODO regarding hyper to DataPlane as well --- router/dataplane.go | 1 + 1 file changed, 1 insertion(+) diff --git a/router/dataplane.go b/router/dataplane.go index b2f4a5267..430886ca7 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -152,6 +152,7 @@ type BatchConn interface { // Return whether all the underlying data that needs to be low for the // computation of `WriteTo`, `Close` is low. // We add this here to match the `Conn` interface in `private/underlay/conn`. + // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. // @ ghost // @ requires Mem() // @ decreases From 7bad72975eb941c809e0d90080bd75d68599d775 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 20:00:07 +0200 Subject: [PATCH 096/104] Address PR feedback --- pkg/slayers/path/hopfield_spec.gobra | 7 +------ pkg/slayers/path/mac.go | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index c2de1d8d7..f5c487dc1 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -115,12 +115,7 @@ pure func (h HopField) Abs() (io.HF) { HVF: AbsMac(h.Mac), } } -// NOTE[henri]: Once Gobra issue 883 is resolved, these could be removed in -// favor of unfolding permissions directly in the contract. -// - We might prefer such getter functions anyway, though. -// - Alternatively, we could also have a "`Dereference`" function returning -// the underlying `HopField` which we could then access. -// This way, we would only need to introduce 1 additional function here. + ghost requires h.Mem() decreases diff --git a/pkg/slayers/path/mac.go b/pkg/slayers/path/mac.go index bce6da97b..f24e771c3 100644 --- a/pkg/slayers/path/mac.go +++ b/pkg/slayers/path/mac.go @@ -30,7 +30,7 @@ const MACBufferSize = 16 // this method does not modify info or hf. // Modifying the provided buffer after calling this function may change the returned HopField MAC. // @ requires h != nil && h.Mem() -// @ requires low(len(buffer) < MACBufferSize) +// @ requires low(len(buffer)) // @ preserves len(buffer) >= MACBufferSize ==> sl.Bytes(buffer, 0, len(buffer)) // @ ensures h.Mem() // @ decreases @@ -48,7 +48,7 @@ func MAC(h hash.Hash, info InfoField, hf HopField, buffer []byte) [MacLen]byte { // Modifying the provided buffer after calling this function may change the returned HopField MAC. // In contrast to MAC(), FullMAC returns all the 16 bytes instead of only 6 bytes of the MAC. // @ requires h != nil && h.Mem() -// @ requires low(len(buffer) < MACBufferSize) +// @ requires low(len(buffer)) // @ preserves len(buffer) >= MACBufferSize ==> sl.Bytes(buffer, 0, len(buffer)) // @ ensures h.Mem() // @ ensures len(res) == MACBufferSize && sl.Bytes(res, 0, MACBufferSize) From b53945a1b0f8b964fe76190d2f17dfeda1d83194 Mon Sep 17 00:00:00 2001 From: henriman Date: Mon, 18 Aug 2025 20:22:25 +0200 Subject: [PATCH 097/104] Add TODO to IsLow regarding adding hyper --- pkg/slayers/path/path.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 58ef05328..cc6da9452 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -75,6 +75,7 @@ type Path interface { // a new instance of a predicate. //@ pred NonInitMem() + // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. //@ ghost //@ requires Mem(ub) //@ decreases From 5598aa78936477aece85585d879536948d1baa29 Mon Sep 17 00:00:00 2001 From: henriman Date: Tue, 19 Aug 2025 12:00:03 +0200 Subject: [PATCH 098/104] Use abstract functions instead of predicate and clean up --- pkg/slayers/path/empty/empty_spec.gobra | 8 +++++ pkg/slayers/path/onehop/onehop.go | 14 +++----- pkg/slayers/path/onehop/onehop_spec.gobra | 43 +++++++++++++---------- pkg/slayers/path/path.go | 15 ++++---- pkg/slayers/path/path_spec.gobra | 9 +++-- pkg/slayers/path/scion/decoded_spec.gobra | 7 +++- pkg/slayers/path/scion/raw_spec.gobra | 8 ++++- 7 files changed, 65 insertions(+), 39 deletions(-) diff --git a/pkg/slayers/path/empty/empty_spec.gobra b/pkg/slayers/path/empty/empty_spec.gobra index a4bd86d16..782357c43 100644 --- a/pkg/slayers/path/empty/empty_spec.gobra +++ b/pkg/slayers/path/empty/empty_spec.gobra @@ -25,6 +25,13 @@ pred (e Path) Mem(buf []byte) { len(buf) == 0 } pred (e Path) NonInitMem() { true } +ghost +requires e.Mem(ub) +decreases +pure func (e Path) IsLow(ub []byte) bool { + return true +} + ghost requires e.Mem(buf) ensures e.NonInitMem() @@ -51,4 +58,5 @@ Path implements path.Path // Definitions to allow *Path to be treated as a path.Path pred (e *Path) Mem(buf []byte) { acc(e) && len(buf) == 0 } pred (e *Path) NonInitMem() { true } + (*Path) implements path.Path \ No newline at end of file diff --git a/pkg/slayers/path/onehop/onehop.go b/pkg/slayers/path/onehop/onehop.go index 8f4ceb3f1..040188311 100644 --- a/pkg/slayers/path/onehop/onehop.go +++ b/pkg/slayers/path/onehop/onehop.go @@ -29,8 +29,6 @@ const PathLen = path.InfoLen + 2*path.HopLen const PathType path.Type = 2 -// TODO: Once Gobra issue 878 is resolved, remove `truested`. -// @ trusted // @ requires path.PkgMem() // @ requires path.RegisteredTypes().DoesNotContain(int64(PathType)) // @ ensures path.PkgMem() @@ -66,7 +64,7 @@ type Path struct { } // @ requires o.NonInitMem() -// @ requires low(len(data) < PathLen) +// @ requires low(len(data)) // @ preserves acc(sl.Bytes(data, 0, len(data)), R42) // @ ensures (len(data) >= PathLen) == (r == nil) // @ ensures r == nil ==> o.Mem(data) @@ -101,7 +99,7 @@ func (o *Path) DecodeFromBytes(data []byte) (r error) { return r } -// @ requires low(len(b) < PathLen) +// @ requires low(len(b)) // @ preserves acc(o.Mem(ubuf), R1) // @ preserves acc(sl.Bytes(ubuf, 0, len(ubuf)), R1) // @ preserves sl.Bytes(b, 0, len(b)) @@ -140,7 +138,7 @@ func (o *Path) SerializeTo(b []byte /*@, ubuf []byte @*/) (err error) { // ToSCIONDecoded converts the one hop path in to a normal SCION path in the // decoded format. // @ requires o.Mem(ubuf) -// @ requires low(o.GetSecondHopConsIngress(ubuf) == 0) +// @ requires low(o.GetSecondHopConsIngress(ubuf)) // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) // @ ensures o.Mem(ubuf) // @ ensures err == nil ==> (sd != nil && sd.Mem(ubuf)) @@ -205,9 +203,7 @@ func (o *Path) ToSCIONDecoded( /*@ ghost ubuf []byte @*/ ) (sd *scion.Decoded, e } // Reverse a OneHop path that returns a reversed SCION path. -// @ requires o.Mem(ubuf) -// requires low(o.GetSecondHopConsIngress(ubuf) == 0) -// @ requires o.LowReverse() +// @ requires o.Mem(ubuf) && o.IsLow(ubuf) // @ preserves sl.Bytes(ubuf, 0, len(ubuf)) // @ ensures err == nil ==> p != nil // @ ensures err == nil ==> p.Mem(ubuf) @@ -215,7 +211,7 @@ func (o *Path) ToSCIONDecoded( /*@ ghost ubuf []byte @*/ ) (sd *scion.Decoded, e // @ ensures err != nil ==> err.ErrorMem() // @ decreases func (o *Path) Reverse( /*@ ghost ubuf []byte @*/ ) (p path.Path, err error) { - // @ o.GetLowReverse(ubuf, 1/2) + //@ o.RevealIsLow(ubuf, writePerm) sp, err := o.ToSCIONDecoded( /*@ ubuf @*/ ) if err != nil { return nil, serrors.WrapStr("converting to scion path", err) diff --git a/pkg/slayers/path/onehop/onehop_spec.gobra b/pkg/slayers/path/onehop/onehop_spec.gobra index 5832ea7b0..e917feaf1 100644 --- a/pkg/slayers/path/onehop/onehop_spec.gobra +++ b/pkg/slayers/path/onehop/onehop_spec.gobra @@ -32,6 +32,30 @@ pred (o *Path) Mem(ubuf []byte) { PathLen <= len(ubuf) } +ghost +requires o.Mem(ub) +decreases +pure func (o *Path) GetSecondHopConsIngress(ghost ub []byte) uint16 { + return unfolding o.Mem(ub) in + unfolding o.SecondHop.Mem() in o.SecondHop.ConsIngress +} + +// TODO: Once Gobra issue #846 is resolved, implement `IsLow`. +ghost +trusted +requires o.Mem(ub) +decreases +pure func (o *Path) IsLow(ub []byte) bool + +ghost +trusted +requires p > 0 +requires acc(o.Mem(ub), p) && o.IsLow(ub) +ensures acc(o.Mem(ub), p) +ensures low(o.GetSecondHopConsIngress(ub)) +decreases +func (o *Path) RevealIsLow(ub []byte, p perm) + ghost requires p.Mem(buf) ensures p.NonInitMem() @@ -65,22 +89,3 @@ pure func (p *Path) LenSpec(ghost ub []byte) int { } (*Path) implements path.Path - -// TODO: Once Gobra issue 883 is removed, this could be removed in favor of -// unfolding permissions directly in the contract. -ghost -requires o.Mem(ub) -decreases -pure func (o *Path) GetSecondHopConsIngress(ghost ub []byte) uint16 { - return unfolding o.Mem(ub) in - unfolding o.SecondHop.Mem() in o.SecondHop.ConsIngress -} - -pred (o *Path) LowReverse() - -ghost -requires o.LowReverse() && p > 0 -preserves acc(o.Mem(ub), p) -ensures low(o.GetSecondHopConsIngress(ub) == 0) -decreases -func (o *Path) GetLowReverse(ghost ub []byte, ghost p perm) diff --git a/pkg/slayers/path/path.go b/pkg/slayers/path/path.go index 7edbd979a..13f60ef14 100644 --- a/pkg/slayers/path/path.go +++ b/pkg/slayers/path/path.go @@ -72,6 +72,13 @@ type Path interface { // (VerifiedSCION) Must imply the resources required to initialize // a new instance of a predicate. //@ pred NonInitMem() + + // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. + //@ ghost + //@ requires Mem(ub) + //@ decreases + //@ pure IsLow(ub []byte) bool + // SerializeTo serializes the path into the provided buffer. // (VerifiedSCION) There are implementations of this interface that modify the underlying // structure when serializing (e.g. scion.Raw) @@ -94,11 +101,6 @@ type Path interface { //@ ensures err != nil ==> NonInitMem() //@ decreases DecodeFromBytes(b []byte) (err error) - // TODO: Once Gobra issue 846 is resolved, rework this. - // Predicate encapsulating sensitivity. Intended to be implemented as an - // abstract predicate, in conjunction with an abstract function requiring - // LowReverse() and ensuring the corresponding sensitivity. - //@ pred LowReverse() //@ ghost //@ pure //@ requires Mem(b) @@ -107,8 +109,7 @@ type Path interface { //@ IsValidResultOfDecoding(b []byte) bool // Reverse reverses a path such that it can be used in the reversed direction. // XXX(shitz): This method should possibly be moved to a higher-level path manipulation package. - //@ requires Mem(ub) - //@ requires LowReverse() + //@ requires Mem(ub) && IsLow(ub) //@ preserves sl.Bytes(ub, 0, len(ub)) //@ ensures e == nil ==> p != nil //@ ensures e == nil ==> p.Mem(ub) diff --git a/pkg/slayers/path/path_spec.gobra b/pkg/slayers/path/path_spec.gobra index 79948a49d..f96144402 100644 --- a/pkg/slayers/path/path_spec.gobra +++ b/pkg/slayers/path/path_spec.gobra @@ -31,6 +31,13 @@ pred (r *rawPath) NonInitMem() { acc(r) } +ghost +requires r.Mem(ub) +decreases +pure func (r *rawPath) IsLow(ub []byte) bool { + return true +} + ghost requires p.Mem(buf) ensures p.NonInitMem() @@ -55,8 +62,6 @@ pure func (p *rawPath) LenSpec(ghost ub []byte) (l int) { len(p.raw) } -pred (r *rawPath) LowReverse() - (*rawPath) implements Path /** End of rawPath spec **/ diff --git a/pkg/slayers/path/scion/decoded_spec.gobra b/pkg/slayers/path/scion/decoded_spec.gobra index bc14af13a..131d1a5d5 100644 --- a/pkg/slayers/path/scion/decoded_spec.gobra +++ b/pkg/slayers/path/scion/decoded_spec.gobra @@ -47,7 +47,12 @@ pred (d *Decoded) Mem(ubuf []byte) { (forall i int :: { &d.HopFields[i] } 0 <= i && i < len(d.HopFields) ==> d.HopFields[i].Mem()) } -pred (d *Decoded) LowReverse() +ghost +requires d.Mem(ub) +decreases +pure func (d *Decoded) IsLow(ub []byte) bool { + return true +} /**** End of Predicates ****/ diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index f99d3cd6a..a98468c67 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -38,7 +38,13 @@ pred (s *Raw) Mem(buf []byte) { len(s.Raw) == s.Base.Len() } -pred (s *Raw) LowReverse() +ghost +requires s.Mem(ub) +decreases +pure func (s *Raw) IsLow(ub []byte) bool { + return true +} + /**** End of Predicates ****/ (*Raw) implements path.Path From c72c8779212b41b69a888bb8d0af6d8c2ba19b61 Mon Sep 17 00:00:00 2001 From: henriman Date: Fri, 19 Sep 2025 10:15:39 +0200 Subject: [PATCH 099/104] Align contract clauses --- private/topology/linktype.go | 10 +++++----- verification/utils/sif/assumptions.gobra | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/private/topology/linktype.go b/private/topology/linktype.go index 5bbd0c9d0..e7cdff685 100644 --- a/private/topology/linktype.go +++ b/private/topology/linktype.go @@ -101,14 +101,14 @@ func (l LinkType) MarshalText() (res []byte, err error) { } } -// @ requires acc(sl.Bytes(data, 0, len(data)), R15) -// @ requires low(len(data)) && +// @ requires acc(sl.Bytes(data, 0, len(data)), R15) +// @ requires low(len(data)) && // @ forall i int :: { sl.GetByte(data, 0, len(data), i) } 0 <= i && i < len(data) && low(i) ==> // @ low(sl.GetByte(data, 0, len(data), i)) // @ preserves acc(l) -// @ ensures acc(sl.Bytes(data, 0, len(data)), R15) -// @ ensures err != nil ==> err.ErrorMem() -// @ ensures low(err != nil) +// @ ensures acc(sl.Bytes(data, 0, len(data)), R15) +// @ ensures err != nil ==> err.ErrorMem() +// @ ensures low(err != nil) // @ decreases func (l *LinkType) UnmarshalText(data []byte) (err error) { //@ unfold acc(sl.Bytes(data, 0, len(data)), R16) diff --git a/verification/utils/sif/assumptions.gobra b/verification/utils/sif/assumptions.gobra index 2afbf9fed..c0cd79938 100644 --- a/verification/utils/sif/assumptions.gobra +++ b/verification/utils/sif/assumptions.gobra @@ -6,6 +6,6 @@ package sif ghost requires p > 0 && acc(b, p) requires low(len(b)) && forall i int :: { &b[i] } 0 <= i && i < len(b) && low(i) ==> low(b[i]) -ensures acc(b, p) && low(string(b)) +ensures acc(b, p) && low(string(b)) decreases func LowSliceImpliesLowString(b []byte, p perm) From 64814a89675c46aa7057551a0f37be17f2d65026 Mon Sep 17 00:00:00 2001 From: henriman Date: Fri, 19 Sep 2025 10:30:50 +0200 Subject: [PATCH 100/104] Remove addressed NOTE[henri] comments --- pkg/addr/host.go | 1 - pkg/addr/host_spec.gobra | 4 ---- pkg/private/serrors/serrors_spec.gobra | 3 --- 3 files changed, 8 deletions(-) diff --git a/pkg/addr/host.go b/pkg/addr/host.go index f795ca602..e4464cf83 100644 --- a/pkg/addr/host.go +++ b/pkg/addr/host.go @@ -92,7 +92,6 @@ const ( type HostAddr interface { //@ pred Mem() - // NOTE[henri]: I rewrote this comment to be more accurate // Return whether all the underlying data that needs to be low for the // computation of `Pack`, `IP`, `String` is low. // TODO: Once Gobra issue #955 is resolved, mark as `hyper`. diff --git a/pkg/addr/host_spec.gobra b/pkg/addr/host_spec.gobra index ed9dfcc11..975fe6ffe 100644 --- a/pkg/addr/host_spec.gobra +++ b/pkg/addr/host_spec.gobra @@ -25,8 +25,6 @@ import ( pred (h HostNone) Mem() { len(h) == HostLenNone } -// NOTE[henri]: As `HostNone.String` doesn't need anything to be low, I -// implemented it trivially already. ghost requires h.Mem() decreases @@ -75,8 +73,6 @@ pred (h HostIPv6) Mem() { slices.Bytes(h, 0, len(h)) } -// NOTE[henri]: As `HostIPv6.String` doesn't need anything to be low, I -// implemented it trivially already. ghost requires h.Mem() decreases diff --git a/pkg/private/serrors/serrors_spec.gobra b/pkg/private/serrors/serrors_spec.gobra index 7c6bd2518..c08711af8 100644 --- a/pkg/private/serrors/serrors_spec.gobra +++ b/pkg/private/serrors/serrors_spec.gobra @@ -76,9 +76,6 @@ func Wrap(msg, cause error, errCtx ...interface{}) (res error) // Elements of errCtx are limited to "primitive types" at the moment. // This is a safe but strict under-approximation of what can be done // with this method. -// NOTE[henri]: As discussed, I took out annotations such as `low(len(errCtx))` -// that were intended to satisfy the technical requirement of the simple -// encoding that control flow has to be low. requires cause != nil ==> cause.ErrorMem() preserves forall i int :: { &errCtx[i] } 0 <= i && i < len(errCtx) ==> acc(&errCtx[i], R15) // The following precondition cannot be adequately captured in Gobra. From f93c44992d2c440d95b17ddc8cf838db13205cdd Mon Sep 17 00:00:00 2001 From: henriman Date: Fri, 19 Sep 2025 10:54:10 +0200 Subject: [PATCH 101/104] Address PR feedback --- private/underlay/conn/conn.go | 2 +- private/underlay/conn/conn_spec.gobra | 4 ++-- private/underlay/sockctrl/sockopt.go | 2 +- verification/utils/definitions/definitions.gobra | 4 ---- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 396d3f6f0..0eb94149a 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -437,7 +437,7 @@ func (c *connUDPBase) Write(b []byte /*@, ghost underlyingConn *net.UDPConn @*/) // @ ensures err == nil ==> 0 <= n && n <= len(b) // @ ensures err != nil ==> err.ErrorMem() func (c *connUDPBase) WriteTo(b []byte, dst *net.UDPAddr /*@, ghost underlyingConn *net.UDPConn @*/) (n int, err error) { - //@ c.RevealIsLow(true, anyPerm, true) + //@ c.RevealIsLow(true, R56, true) //@ unfold acc(c.Mem(), _) if c.Remote != nil { return c.conn.Write(b) diff --git a/private/underlay/conn/conn_spec.gobra b/private/underlay/conn/conn_spec.gobra index 04c832909..909a23c0e 100644 --- a/private/underlay/conn/conn_spec.gobra +++ b/private/underlay/conn/conn_spec.gobra @@ -226,7 +226,7 @@ ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv4) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { c.RevealIsLow(true) unfold acc(c.Mem(), _) - c.connUDPBase.RevealIsLow(true, anyPerm, false) + c.connUDPBase.RevealIsLow(true, R56, false) unfold acc(c.connUDPBase.MemWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn @@ -318,7 +318,7 @@ ensures err != nil ==> err.ErrorMem() func (c *connUDPIPv6) WriteTo(b []byte, dst *net.UDPAddr) (n int, err error) { c.RevealIsLow(true) unfold acc(c.Mem(), _) - c.connUDPBase.RevealIsLow(true, anyPerm, false) + c.connUDPBase.RevealIsLow(true, R56, false) unfold acc(c.connUDPBase.MemWithoutConn(), _) assert c.pconn.GetUnderlyingConn() == c.conn tmpImpl := c.conn diff --git a/private/underlay/sockctrl/sockopt.go b/private/underlay/sockctrl/sockopt.go index fb2013ec7..cef94a8e5 100644 --- a/private/underlay/sockctrl/sockopt.go +++ b/private/underlay/sockctrl/sockopt.go @@ -27,7 +27,7 @@ import ( // @ requires low(level) && low(opt) // @ preserves c.Mem() && c.IsLow() // @ ensures e != nil ==> e.ErrorMem() -// @ ensures low(r) && low(e) +// @ ensures low(r) && low(e != nil) // @ decreases _ func GetsockoptInt(c *net.UDPConn, level, opt int) (r int, e error) { var val int diff --git a/verification/utils/definitions/definitions.gobra b/verification/utils/definitions/definitions.gobra index ca024f9e8..3b458ff59 100644 --- a/verification/utils/definitions/definitions.gobra +++ b/verification/utils/definitions/definitions.gobra @@ -76,10 +76,6 @@ const ( R54 R55 R56 - // Some functions have parameters `wildcard bool` and `p perm`, and consume - // a wildcard permission amount if `wildcard` is set, and amount `p` - // otherwise. If `wildcard` is set, `p` should be set to `anyPerm`. - anyPerm ) // To be used as a precondition of functions and methods that can never be called From b807f2bc2a126f4ebf82e96a922d434ade828465 Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 20 Sep 2025 14:38:54 +0200 Subject: [PATCH 102/104] Remove `trusted` from `UDPConn.IsLow` As `UDPConn` is "abstract" (i.e. only defined using `PrivateField`), the issue here is not of not being able to implement `IsLow`, only that we still need to add `hyper` to it. Update comment accordingly. --- verification/dependencies/net/udpsock.gobra | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/verification/dependencies/net/udpsock.gobra b/verification/dependencies/net/udpsock.gobra index ee0473a6a..7c579863b 100644 --- a/verification/dependencies/net/udpsock.gobra +++ b/verification/dependencies/net/udpsock.gobra @@ -90,11 +90,10 @@ pred (u *UDPConn) Mem() { acc(u) } -// TODO: Once Gobra issue #846 is resolved, actually implement `IsLow`. -// - Here, `IsLow` will need to cover all fields (of `UDPConn`), since we use -// this information in private/underlay/sockctrl/sockopt.go, `GetsockoptInt`. +// TODO: Once Gobra issue #846 is resolved, mark as `hyper`. +// We assume that `IsLow` covers all fields of `UDPConn`, since we use this +// information in `private/underlay/sockctrl/sockopt.go`, `GetsocketoptInt` ghost -trusted requires c.Mem() decreases pure func (c *UDPConn) IsLow() bool From 3eab3a9fb4086fba4fd605271cff62a488c50ca1 Mon Sep 17 00:00:00 2001 From: henriman Date: Sat, 20 Sep 2025 14:45:25 +0200 Subject: [PATCH 103/104] Fix comment on `NewReadMessages` --- private/underlay/conn/conn.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index 0eb94149a..2a02e9a8c 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -479,8 +479,7 @@ func (c *connUDPBase) Close() (err error) { // NewReadMessages allocates memory for reading IPv4 Linux network stack // messages. // NOTE: The verification of this function is somewhat unstable. -// See Gobra issue #957. -// this further. +// Gobra issue #957 tracks this instability. // @ requires 0 < n // @ requires low(n) // @ ensures len(res) == n From bb199401e5d7910adc29c3d8077763a47f894173 Mon Sep 17 00:00:00 2001 From: henriman Date: Thu, 25 Sep 2025 23:14:55 +0200 Subject: [PATCH 104/104] Clean up --- pkg/slayers/path/hopfield.go | 1 - pkg/slayers/path/hopfield_spec.gobra | 4 +++- pkg/slayers/path/onehop/onehop_spec.gobra | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 43f43c1a0..e9e75e06b 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -110,7 +110,6 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { // SerializeTo writes the fields into the provided buffer. The buffer must be of length >= // path.HopLen. // @ requires len(b) >= HopLen -// TODO: is this part of some interface? // @ requires acc(h.Mem(), R10) && h.IsLow() // @ preserves sl.Bytes(b, 0, HopLen) // @ ensures acc(h.Mem(), R10) diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index e7604f4e5..959c232aa 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -41,13 +41,15 @@ pure func (h *HopField) GetIngressRouterAlert() bool { return unfolding h.Mem() in h.IngressRouterAlert } -// TODO: add TODO comment +// TODO: Once Gobra issue #846 is resolved, implement this. ghost trusted requires h.Mem() decreases pure func (h *HopField) IsLow() bool +// "Reveal the body" of `h.IsLow` (necessary bc. we can't implement `h.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. ghost trusted requires p > 0 diff --git a/pkg/slayers/path/onehop/onehop_spec.gobra b/pkg/slayers/path/onehop/onehop_spec.gobra index 970e67502..c96e07370 100644 --- a/pkg/slayers/path/onehop/onehop_spec.gobra +++ b/pkg/slayers/path/onehop/onehop_spec.gobra @@ -47,7 +47,6 @@ pure func (o *Path) GetInfo(ghost ub []byte) path.InfoField { return unfolding o.Mem(ub) in o.Info } -// TODO: really unsure whether this is the best way to handle this ghost requires o.Mem(ub) decreases @@ -69,6 +68,8 @@ requires o.Mem(ub) decreases pure func (o *Path) IsLow(ub []byte) bool +// "Reveal the body" of `o.IsLow` (necessary bc. we can't implement `o.IsLow` yet). +// TODO: Once Gobra issue #846 is resolved, implement this. ghost trusted requires p > 0