Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MWE for proving SIF #386

Closed
wants to merge 0 commits into from
Closed

Conversation

henriman
Copy link

Not supposed to be merged. (Very) minimal working example of how to verify secure information flow for the router

@jcp19 jcp19 marked this pull request as draft December 2, 2024 20:20
@jcp19 jcp19 marked this pull request as ready for review December 2, 2024 22:39
Comment on lines 103 to 121
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))})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type Collect introduced here does not seem to be used anywhere. As such, I think we should define these operations not through a domain, but with pure functions instead. Please check if this applies to other domains you introduced as well

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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this produce a verification error? I would have expected that the call r.Guard(s, a) can only succeed if r is not nil

Copy link
Author

@henriman henriman Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not produce a verification error as is. Only when moving the call r.Guard(s, a) to a pure function (while trying to extract the Restriction domain / Reaches), Gobra complains

Comment on lines 240 to 243
/* ghost */ Declassify(t)
/* ghost */ LowPost(DefaultClassification{})
Send(t)
/* ghost */ LowPost(DefaultClassification{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a comment here about what would happen if we, for example, forgot to call LowPost(DefaultClassification{}) or Declassify in any of the three places where we currently do. The interesting point is that we would get a verification error in the postcondition instead of in the the call to Send

ensures token(old(Recv_T(p)))
ensures m == old(Recv_R(p))
ensures low(m)
func Recv(ghost p Place) (m int)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would return the new place here as a ghost return value

requires token(p) && SendPerm(p, t)
requires low(t)
ensures token(old(Send_T(p, t)))
func Send(ghost p Place, t int)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here and in declassify

Comment on lines 67 to 74
// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might want to simplify this with let bindings to introduce intermediate variables for the places and for the mac

Comment on lines 80 to 81
lastMsg2 int // 2nd
lastMsg3 int // 3rd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we introduce lastMsg2 and lastMsg3 here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below. Protocol2 allows sending the MAC tag of either the most or second most recently received message, and to ensure this, we need to keep track of these in our state. lastMsg3 is actually not needed, however, and I removed it. I put it in initially to make it more explicit which messages' tags can be sent and which can't.

Comment on lines 84 to 93
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this protocol show sth that the other did not?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I constructed Protocol2 to show a protocol ...

  1. ... which allows declassifying different values at any point, and thus needs the additional parameter p for declassification (to make the IOD spec deterministic and the low data, which includes p).
  2. ... where one is allowed by the IOD spec to send something before declassifying, which is only "caught" (in case it was high data) by the classification spec / pre- and postconditions of the I/O functions (in the other protocol, one had to declassify before sending just to conform to the IOD spec). This is supposed to exemplify that SIF goes beyond simply following the IOD spec.
  3. ... which contains more complex, mutable state, and how to work with it.

@jcp19
Copy link
Contributor

jcp19 commented Jan 13, 2025

It may be worth to move these examples to the test cases of viperproject/gobra#802

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants