Presentation is loading. Please wait.

Presentation is loading. Please wait.

Mobility, Security, and Proof-Carrying Code Peter Lee Carnegie Mellon University Lecture 4 July 13, 2001 LF, Oracle Strings, and Proof Tools Lipari School.

Similar presentations


Presentation on theme: "Mobility, Security, and Proof-Carrying Code Peter Lee Carnegie Mellon University Lecture 4 July 13, 2001 LF, Oracle Strings, and Proof Tools Lipari School."— Presentation transcript:

1 Mobility, Security, and Proof-Carrying Code Peter Lee Carnegie Mellon University Lecture 4 July 13, 2001 LF, Oracle Strings, and Proof Tools Lipari School on Foundations of Wide Area Network Programming

2 “Applets, Not Craplets” A Demo

3 Cedilla Systems Architecture Code producerHost Ginseng Native code Proof Special J Java binary ~52KB, written in CWritten in OCaml

4 Annotations Cedilla Systems Architecture Code producerHost Proof checker VCGen Axioms Native code Proof VC Special J Java binary

5 Annotations Cedilla Systems Architecture Code producerHost Java binary Proof generator Proof checker VCGen Axioms Certifying compiler VCGen VC Native code Proof VC

6 Java Virtual Machine JVM Java Verifier JNI Class file Native code Proof- carrying code Checker

7 Show either the Mandelbrot or NBody3D demo.

8 Crypto Test Suite Results [Cedilla Systems] sec On average, 158% faster than Java, 72.8% faster than Java with a JIT.

9 Java Grande Suite v2.0 [Cedilla Systems] sec

10 Java Grande Bench Suite [Cedilla Systems] ops

11 Ginseng VCGen Checker Safety Policy Dynamic loading Cross-platform support ~15KB, roughly similar to a KVM verifier (but with floating-point). ~4KB, generic. ~19KB, declarative and machine-generated. ~22KB, some optional.

12 Practical Considerations

13 Trusted Computing Base The trusted computing base is the software infrastructure that is responsible for ensuring that only safe execution is possible. Obviously, any bugs in the TCB can lead to unsafe execution. Thus, we want the TCB to be simple, as well as fast and small.

14 VCGen’s Complexity Fortunately, we shall see that proofchecking can be quite simple, small, and fast. VCGen, at core, is also simple and fast. But in practice it gets to be quite complicated.

15 VCGen’s Complexity Some complications: If dealing with machine code, then VCGen must parse machine code. Maintaining the assumptions and current context in a memory- efficient manner is not easy. Note that Sun’s kVM does verification in a single pass and only 8KB RAM!

16 VC Explosion a == b a == c f(a,c) a := xc := x a := yc := y a=b => (x=c => safe f (y,c)  x<>c => safe f (x,y))  a<>b => (a=x => safe f (y,x)  a<>x => safe f (a,y)) Exponential growth in size of the VC is possible.

17 VC Explosion a == b a == c f(a,c) a := xc := x a := yc := y INV: P(a,b,c,x) (a=b => P(x,b,c,x)  a<>b => P(a,b,x,x))  (  a’,c’. P(a’,b,c’,x) => a’=c’ => safe f (y,c’)  a’<>c’ => safe f (a’,y)) Growth can usually be controlled by careful placement of just the right “join-point” invariants.

18 Stack Slots Each procedure will want to use the stack for local storage. This raises a serious problem because a lot of information is lost by VCGen (such as the value) when data is stored into memory. We avoid this problem by assuming that procedures use up to 256 words of stack as registers.

19 Exercise 8. Just as with loop invariants, our actual join-point invariants includes a specification of the registers that might be modified since the dominating block. Why might this be a useful thing to do? Why might it be a bad thing to do?

20 Callee-save Registers Standard calling conventions dictate that the contents of some registers be preserved. These callee-save registers are specified along with the pre/post- conditions for each procedure. The preservation of their values must be verified at every return instruction.

21 Introduction to Efficient Representation and Validation of Proofs

22 High-Level Architecture Explanation Code Verification condition generator Checker Safety policy Agent Host

23 Goals We would like a representation for proofs that is compact, fast to check, requires very little memory to check, and is “canonical” (in the sense of accommodating many different logics without requiring a total reimplementation of the checker).

24 Three Approaches 1. Direct representation of a logic. 2. Use of a Logical Framework. 3. Oracle strings. We will reject (1). Today we introduce (2) and (3).

25 Logical Framework For representation of proofs we use the Edinburgh Logical Framework (LF).

26 Reynolds’ Example Skip?

27 Formal Proofs Write “x is a proof of P” as x:P. Examples of predicates P: (for all A, B) A and B => B and A (for all x, y, z) x x < z What do the proofs look like?

28 Inference Rules We can write proofs by stitching together inference rules. An example inference rule: If we have a proof x of P and a proof y of Q, then x and y together constitute a proof of P  Q. Or, more compactly:  if x:P, y:Q then (x,y):P*Q. A proof, written in our compact notation.

29 More Inference Rules Another inference rule: Assume we have a proof x of P. If we can then obtain a proof b of Q, then we have a proof of P  Q. if [x:P] b:Q then fn (x:P) => b : P  Q. More rules:  if x:P*Q then fst(x):P  if y:P*Q then snd(y):Q

30 Types and Proofs So, for example: fn (x:P*Q) => (snd(x), fst(x)) : P*Q  Q*P We can develop a full metalanguage based on this principle of “proofs as programs”. Typechecking gives us proofchecking! Codified in the LF language.

31 LF i Skip?

32 LF Example This classic example illustrates how LF is used to represent the terms and rules of a logical system.

33 LF Example in Elf Syntax exp : type pred : type pf : pred -> type true : pred /\ : pred -> pred -> pred => : pred -> pred -> pred all : (exp -> pred) -> pred truei : pf true andi : {P:pred} {R:pred} pf P -> pf R -> pf (/\ P R) andel : {P:pred} {R:pred} pf (/\ P R) -> pf P impi : {P:pred} {R:pred} (pf P -> pf R) -> pf (=> P R) alli : {P:exp -> pred} ({X:exp} pf (P X)) -> pf (all P) alle : {P:exp -> pred} {E:exp} pf (all P) -> pf (P E) The same example, but using Pfenning’s Elf syntax.

34 LF as a Proof Representation LF is canonical, in that a single typechecker for LF can serve as a proofchecker for many different logics specified in LF. [See Avron, et al. ‘92] But the efficiency of the representation is poor.

35 Size of LF Representation Proofs in LF are extremely large, due to large amounts of repetition. Consider the representation of P  P  P for some predicate P: The proof of this predicate has the following LF representation: (=> P (/\ P P )) (impi P (/\ P P ) ([X:pf P ] andi P P x x))

36 Checking LF The nice thing is that typechecking is enough for proofchecking. [The theorem is in the LF paper.] But the proofs are extremely large. (impi P (/\ P P ) ([X:pf P ] andi P P X X)) : pf (=> P (/\ P P ))

37 Implicit LF A dramatic improvement can be achieved by using a variant of LF, called Implicit LF, or LF i. In LF i, parts of the proof can be replaced by placeholders. (impi * * ([X:*] andi * * X X)) : pf (=> P (/\ P P ))

38 Soundness of LF i The soundness of the LF i type system is given by a theorem that states: If, in context , a term M has type A in LF i (and  and A are placeholder-free), then there is a term M’ such that M’ has type A in LF.

39 Typechecking LF i The typechecking algorithm for LF i is given in [Necula & Lee, LICS98]. A key aspect of the algorithm is that it avoids repeated typechecking of reconstructed terms. Hence, the placeholders save not only space, but also time.

40 Effectiveness of LF i In experiments with PCC, LF i leads to substantial reductions in proof size and checking time. Improvements increase nonlinearly with proof size.

41 The Need for Improvement Despite the great improvement of LF i, in our experiments we observe that LF i proofs are 10%- 200% the size of the code.

42 How Big is a Proof? A basic question is how much essential information is in a proof? In this proof, there are only 2 uses of rules and in each case they were the only rule that could have been used. (impi * * ([X:*] andi * * x x)) : pf (=> P (/\ P P ))

43 Improving the Representation We will now improve on the compactness of proof representation by making use of the observation that large parts of proofs are deterministically generated from the inference rules.

44 Additional References For LF: Harper, Honsell, & Plotkin. A framework for defining logics. Journal of the ACM, 40(1), 143- 184, Jan. 1993. Avron, Honsell, Mason, & Pollack. Using typed lambda calculus to implement formal systems on a machine. Journal of Automated Reasoning, 9(3), 309-354, 1992.

45 Additional References For Elf: Pfenning. Logic programming in the LF logical framework. Logical Frameworks, Huet & Plotkin (Eds.), 149-181, Cambridge Univ. Press, 1991. Pfenning. Elf: A meta-language for deductive systems (system description). 12th International Conference on Automated Deduction, LNAI 814, 811-815, 1994.

46 Oracle-Based Checking

47 Necula’s Example Syntax of Girard’s System F ty : type int : ty arr : ty -> ty -> ty all : (ty -> ty) -> ty exp : type z : exp s : exp -> exp lam : (exp -> exp) -> exp app : exp -> exp -> exp of : exp -> ty -> type

48 Necula’s Example Typing Rules for System F tz : of z int ts : {E:exp} of E int -> of (s E) int tlam : {E:exp->exp} {T1:ty} {T2:ty} ({X:exp} of X T1 -> of (E X) T2) -> of (lam E) (arr T1 T2) tapp : {E1:exp} {E2:exp} {T:ty} {T2:ty} of E1 (arr T2 T) -> of E2 T2 -> of (app E1 E2) T tgen : {E:exp} {T:ty->ty} ({T1:ty} of E (T T1)) -> of E (all T) tins : {E:exp} {T:ty->ty} {T1:ty} of E (all T) -> of E (T T1)

49 LF Representation Consider the lambda expression It is represented in LF as follows: ( f.(f x.x) (f 0)) y.y app (lam [F:exp] app (app F (lam [X:exp] X)) (app F 0)) (lam [Y:exp] Y)

50 Necula’s Example Now suppose that this term is an applet, with the safety policy that all applets must be well-typed in System F. One way to make a PCC is to attach a typing derivation to the term.

51 Typing Derivation in LF (tapp (lam [F:exp] (app (app F (lam [X:exp] X)) (app F 0))) (lam ([X:exp] X)) (all ([T:ty] arr T T)) int (tlam (all ([T:ty] arr T T)) int ([F:exp] (app (app F (lam [X:exp] X)) (app F 0))) ([F:exp][FT:of F (all ([T:ty] arr T T))] (tapp (app F (lam [X:exp] X)) (app F 0) int int (tapp F (lam [X:exp] X) (arr int int) (arr int int) (tins F ([T:ty] arr T T) (arr int int) FT) (tlam int int ([X:exp] X) ([X:exp][XT:of X int] XT))) (tapp F 0 int int (tins F ([T:ty] arr T T) int FT) t0)))) (tgen (lam [Y:exp] Y) ([T:ty] arr T T) ([T:ty] (tlam T T ([Y:exp] Y) ([Y:exp] [YT:of Y T] YT)))))

52 Typing Derivation in LF i (tapp * * (all ([T:*] arr T T)) int (tlam * * * ([F:*][FT:of F (all ([T:ty] arr T T))] (tapp * * int (tapp * * (arr int int) (arr int int) (tins * * * FT) (tlam * * * ([X:*][XT:*] XT))) (tapp * * int int (tins * * * FT) t0)))) (tgen * * ([T:*] (tlam * * * ([Y:*] [YT:*] YT))))) I think. I did this by hand!

53 LF Representation Using 16 bits per token, the LF representation of the typing derivation requires over 2,200 bits. The LF i representation requires about 700 bits. (The term itself requires only about 360 bits.) Skip ahead

54 A Bit More about LF i To convert an LF term into an LF i term, a representation algorithm is used. [Necula&Lee, LICS98] Intuition: When typechecking a term: c M 1 M 2 … M n : A (in a context ) we know, if A has no placeholders, that some of the M 1 …M n may appear in A.

55 A Bit More about LF i, cont’d For example, when the rule is applied at top level, the first two arguments are present in the term and thus can be elided. tapp : {E1:exp} {E2:exp} {T:ty} {T2:ty} of E1 (arr T2 T) -> of E2 T2 -> of (app E1 E2) T app (lam [F:exp] app (app F (lam [X:exp] X)) (app F 0)) (lam [Y:exp] Y)

56 A Bit More about LF i, cont’d A similar trick works at lower levels by relying on the fact that typing constraints are solved in a certain order (e.g., right-to-left). See the paper for complete details.

57 Can We Do Better? tz : of z int ts : {E:exp} of E int -> of (s E) int tlam : {E:exp->exp} {T1:ty} {T2:ty} ({X:exp} of X T1 -> of (E X) T2) -> of (lam E) (arr T1 T2) tapp : {E1:exp} {E2:exp} {T:ty} {T2:ty} of E1 (arr T2 T) -> of E2 T2 -> of (app E1 E2) T tgen : {E:exp} {T:ty->ty} ({T1:ty} of E (T T1)) -> of E (all T) tins : {E:exp} {T:ty->ty} {T1:ty} of E (all T) -> of E (T T1)

58 Determinism Looking carefully at the typing rules, we observe: For any typing goal where the term is known but the type is not: 3 possibilities: tgen, tins, other. If type structure is also known, only 2 choices, tapp or other.

59 How Much Essential Information? (tapp (lam [F:exp] (app (app F (lam [X:exp] X)) (app F 0))) (lam ([X:exp] X)) (all ([T:ty] arr T T)) int (tlam (all ([T:ty] arr T T)) int ([F:exp] (app (app F (lam [X:exp] X)) (app F 0))) ([F:exp][FT:of F (all ([T:ty] arr T T))] (tapp (app F (lam [X:exp] X)) (app F 0) int int (tapp F (lam [X:exp] X) (arr int int) (arr int int) (tins F ([T:ty] arr T T) (arr int int) FT) (tlam int int ([X:exp] X) ([X:exp][XT:of X int] XT))) (tapp F 0 int int (tins F ([T:ty] arr T T) int FT) t0)))) (tgen (lam [Y:exp] Y) ([T:ty] arr T T) ([T:ty] (tlam T T ([Y:exp] Y) ([Y:exp] [YT:of Y T] YT)))))

60 How Much Essential Information? There are 15 applications of rules in this derivation. So, conservatively: log 2 3  15 = 30 bits In other words, 30 bits should be enough to encode the choices made by a type inference engine for this term.

61 Oracle-based Checking Idea: Implement the proofchecker as a nondeterministic logic interpreter whose program consists of the derivation rules, and initial goal is the judgment to be verified. We will avoid backtracking by relying on the oracle string. Skip ahead

62 Why Higher-Order? The syntax of VCs for the Java type-safety policy is as follows: The LF encodings are simple Horn clauses (and requiring only first- order unification). Higher- order features only for implication and universal quantification. E ::= x | c E 1 … E n F ::= true | F 1  F 2 |  x.F | E | E  F

63 Why Higher-Order? Perhaps first-order Horn logic (or perhaps first-order hereditary Harrop formulas) is enough. Indeed, first-order expressions and formulas seem to be enough for the VCs in type-safety policies. However, higher-order and modal logics would require higher-order features.

64 A Simplification A Fragment of LF Level-0 types.  A ::= a | A 1  A 2 Level-1 types (-normal form).  B ::= a M 1 … M n | B 1  B 2 |  x:A.B Level-0 kinds.  K ::= Type | A  K Level-0 terms (-normal form).  M ::= x:A.M | c M 1 … M n | x M 1 … M n

65 LF Fragment This fragment simplifies matters considerably, without restricting the application to PCC. Level-0 types to encode syntax. Level-1 types to encode derivations. No level-1 terms since we never reconstruct a derivation, only verify that one exists!

66 LF Fragment, cont’d ty : type exp : type of : exp -> ty -> type Level-0 types. Level-1 type family. Disallowing level-2 and higher type families seems not to have any practical impact.

67 Logic Interpreter Goals G ::= B | M = M’ |  x:B.G |  x:A.G | T | G 1  G 2. For Necula’s example, the interpreter will be started with the goal  t : ty. of E t

68 Naïve Interpreter solve( B 1  B 2 ) =  x:B 1. solve( B 2 ) solve(  x:A.B ) =  x:A. solve( B ) solve( a M 1 … M n ) = subgoals( B, a M 1 … M n ) where B is the type of a level-1 constant or a level-1 quantified variable (in scope), as selected by the oracle. subgoals( B 1  B 2, B ) =  x:B 1. solve( B 2 ) subgoals(  x:A.B’, B ) =  x:A. solve( B ) subgoals( a M 1 ’ … M n ’, a M 1 … M n ) = M 1 = M 1 ’  …  M n = M n ’

69 Necula’s Example Consider  solve(of E t ) This consults the oracle. Since there are 3 level-1 constants that could be used at this point, 2 bits are fetched from the oracle string (to select tapp).

70 Higher-Order Unification The unification goals that remain after solve are higher-order and thus only semi-decidable. A nondeterministic unification procedure (also driven by the oracle string) is used. Some standard LP optimizations are also used.

71 Certifying Theorem Proving

72 Time does not allow a description here. See: Necula and Lee. Proof generation in the Touchstone theorem prover. CADE’00. Of particular interest: Proof-generating congruence- closure and simplex algorithms.

73 Certifying Compilation Skip ahead

74 The Basic Trick Recall the bcopy program: public class Bcopy { public static void bcopy(int[] src, int[] dst) { int l = src.length; int i = 0; for(i=0; i<l; i++) { dst[i] = src[i]; }

75 Unoptimized Loop Body L11 : movl4(%ebx), %eax cmpl%eax, %edx jaeL24 L17 : cmpl$0, 12(%ebp) movl8(%ebx, %edx, 4), %esi jeL21 L20 : movl12(%ebp), %edi movl4(%edi), %eax cmpl%eax, %edx jaeL24 L23 : movl%esi, 8(%edi, %edx, 4) movl%edi, 12(%ebp) incl%edx L9 : ANN_INV(ANN_DOM_LOOP, %LF_(/\ (of rm mem ) (of loc1 (jarray jint) ))%_LF, RB(EBP,EBX,ECX,ESP,FTOP,LOC4,LOC3)) cmpl%ecx, %edx jlL11 Bounds check on src. Bounds check on dst. Note: L24 raises the ArrayIndex exception.

76 Unoptimized Code is Easy In the absence of optimizations, proving the safety of array accesses is relatively easy. Indeed, in this case it is reasonable for VCGen to verify the safety of the array accesses. As the optimizer becomes more successful, verification gets harder.

77 Role of Loop Invariants It is for this reason that the optimizer’s knowledge must be conveyed to the theorem prover. Essentially, any facts about program values that were used to perform and code-motion optimizations must be declared in an invariant.

78 Optimized Loop Body L7: ANN_LOOP(INV = { (csubneq ebx 0), (csubneq eax 0), (csubb edx ecx), (of rm mem)}, MODREG = (EDI,EDX,EFLAGS,FFLAGS,RM)) cmpl%esi, %edx jaeL13 movl8(%ebx, %edx, 4), %edi movl%edi, 8(%eax, %edx, 4) incl%edx cmpl%ecx, %edx Essential facts about live variables, used by the compiler to eliminate bounds- checks in the loop body.

79 Certifying Compiling and Proving Intuitively, we will arrange for the Prover to be at least as powerful as the Compiler’s optimizer. Hence, we will expect the Prover to be able to “reverse engineer” the reasoning process that led to the given machine code. An informal concept, needing a formal understanding!

80 What is Safety, Anyway? If the compiler fails to optimize away a bounds-check, it will insert code to perform the check. This means that programs may still abort at run-time, albeit with a well-defined exception. Is this safe behavior?

81

82 Resource Constraints Bounds on certain resources can be enforced via counting. In a Reference Intepreter:  Maintain a global counter.  Increment the count for each instruction executed.  Verify for each instruction that the limit is not exceeded.  Use the compiler to optimize away the counting operations.

83 Compiler as a Theorem-Proving Front-End The compiler is essentially a user- interface to a theorem prover. The possibilities for interactive use of compilers have been described in the literature but not developed very far. PCC may extend the possibilities.

84 Developing PCC Tools

85 Compiler Development The PCC infrastructure catches many (probably most) compiler bugs early. Our standard regression test does not execute the object code! Principle: Most compiler bugs show up as safety violations.

86 Example Bug … L42:movl4(%eax), %edx testl %edx, %edx jleL47 L46:… set up for loop … L44:… enter main loop code … … jl L44 jmp L32 L47:fldz fldz L32:… return sequence … ret

87 Example Bug … L42:movl4(%eax), %edx testl %edx, %edx jleL47 L46:… set up for loop … L44:… enter main loop code … … jl L44 jmp L32 L47:fldz L32:… return sequence … ret Error in rarely executed compensation code is caught by the Proof Generator.

88 Another Example Bug Suppose bcopy’s inner loop is changed: L7: ANN_LOOP( … ) cmpl%esi, %edx jaeL13 movl8(%ebx, %edx, 4), %edi movl%edi, 8(%eax, %edx, 4) incl%edx cmpl%ecx, %edx jlL7 ret

89 Another Example Bug Suppose bcopy’s inner loop is changed: L7: ANN_LOOP( … ) cmpl%esi, %edx jaeL13 movl8(%ebx, %edx, 4), %edi movl%edi, 8(%eax, %edx, 4) addl2, %edx cmpl%ecx, %edx jlL7 ret Again, PCC spots the danger.

90 Yet Another class Floatexc extends Exception { public static int f(int x) throws Floatexc { return x;} public static int g(int x) { return x;} public static float handleit (int x, int y) { float fl=0; try { x=f(x); fl=1; y=f(y); } catch (Floatexc b) { fl+=fl; } return fl; }

91 Yet Another …Install handler… pushl$_6except8Floatexc_C call__Jv_InitClass addl$4, %esp …Enter try block… L17: movl$0, -4(%ebp) pushl8(%ebp) call_6except8Floatexc_MfI addl$4, %esp movl%eax, %ecx … …A handler… L22: flds-4(%ebp) fadds-4(%ebp) jmpL18 … To the end

92 Why PCC May be a Reasonable Idea

93 Ten Good Things About PCC 1. Someone else does all the really hard work. 2. The host system changes very little....

94 Logic as a lingua franca Certifying Prover CPU Code Proof Engine

95 Logic as a lingua franca Certifying Prover CPU Proof Checker Policy VC Code Language/compiler/machine dependences isolated from the proof checker. Expressed as predicates and derivations in a formal logic.

96 Logic as a lingua franca Certifying Prover CPU … iadd iaload... Proof Checker Policy VC Code can be in any language once a Safety Policy is supplied.

97 Logic as a lingua franca Certifying Prover CPU … addl %eax,%ebx testl %ecx,%ecx jz NULLPTR movl 4(%ecx),%edx cmpl %edx,%ebx jae ARRAYBNDS movl 8(%ecx.%ebx.4).%edx... Proof Checker Policy VC … addl %eax, % testl %ecx,%e jz NULLPTR movl 4(%ecx),% cmpl %edx,%eb jae ARRAYBND movl 8(%ecx. Adequacy of dynamic checks and “wrappers” can be verified.

98 Logic as a lingua franca Certifying Prover CPU … add %eax,%ebx movl 8(%ecx,%ebx,4)... Proof Checker Policy VC Safety of optimized code can be verified.

99 Ten Good Things About PCC 3. You choose the language. 4. Optimized (“unsafe”) code is OK. 5. Verifies that your optimizer and dynamic checks are OK. …

100 The Role of Programming Languages Civilized programming languages can provide “safety for free”. Well-formed/well-typed  safe. Idea: Arrange for the compiler to “explain” why the target code it generates preserves the safety properties of the source program.

101 Certifying Compilers [Necula & Lee, PLDI’98] Intuition: Compiler “knows” why each translation step is semantics-preserving. So, have it generate a proof that safety is preserved. “Small theorems about big programs.” Don’t try to verify the whole compiler, but only each output it generates.

102 Automation via Certifying Compilation Certifying Compiler CPU Proof Checker Policy VC Source code Proof Object code Looks and smells like a compiler. % spjc foo.java bar.class baz.c -ljdk1.2.2

103 Ten Good Things About PCC 6. Can sometimes be easy-to-use. 7. You can still be a “hero theorem hacker” if you want....

104 Ten Good Things About PCC 8. Proofs are a “semantic checksum”. 9. Possibility for richer safety policies. 10. Co-exists peacefully with crypto.

105 Acknowledgments George Necula. Robert Harper and Frank Pfenning. Mark Plesko, Fred Blau, and John Gregorski.

106 Microsoft’s ActiveF Technology y o uy o u WANT TO prove TODA y ? WH a T DO


Download ppt "Mobility, Security, and Proof-Carrying Code Peter Lee Carnegie Mellon University Lecture 4 July 13, 2001 LF, Oracle Strings, and Proof Tools Lipari School."

Similar presentations


Ads by Google