Protection thru Procedural Abstraction

The following pattern is old, yet little known. It is a way to attenuate privilege and was available in Algol 60, but not all modern languages. It may be a way to introduce a conceptual bridge between language ideas and capabilities.

We have a mutable array of pairs of integers. We wish to call a routine, granting it the ability to change, at will, the second integer of any element of the array. In gcc:

typedef struct{int X; int Y;} pair;
void adj_up(void set(int ndx, int val));

int main(){pair array[100];
void setY(int x, int y){array[x].Y = y;}
// ...
adj_up(setY);
// ...
return 0;}
The routine main has the authority to change variables it declares and thus has the authority to change array.
main wields that authority in its subroutine setY but attenuates it to only modifying the Y elements. The attenuated authority is delivered to routine adj_up. This pattern is a simple application of the Principle Of Least Privilege (or Authority).

Official C excludes nested procedure definitions and the array declaration would have to be moved to file scope in order that setY can be moved as well. That would not hurt our example but that limitation impacts the ability to scale the pattern to more complex programs. Algol60, PL/I, Scheme, and languages that provide lexical scope and procedure nesting all support the pattern as shown. In particular the language C is not type safe and the code above does not guarantee that the X members of the array are safe from the routine. If we changed setY to

void setY(int x, int y){if(0<=x && x<100) array[x].Y = y;}
the routine adj_up would have to explicitly violate type safety to damage the X’s, but it could do just that.

Routine setY is a wrapper, but not a transparent wrapper, unless the alternative was for adj_up to expect a setter routine for an array. The procedure value passed to adj_up is indistinguishable from such a setter as far as adj_up is concerned.

Now we describe this trivial program with capability jargon to illustrate the concept map. Main creates the array and thus acquires a capability to it depicted by the identifier “array”. Next main builds an object endowed with that capability. The object’s behavior is to accept a message and modify the right half of an array element. The new value and the index for the array element are both specified in the message. The capability to send such messages is formed and bound to “setY”. The new capability is sent to “adj_up”, a capability that main was endowed with upon loading.

To apply this description to Keykos we imagine that the array’s integrity is important enough to separate the programs main and adj_up into different domains. The main code either situates the array in its own segment or among its own variables. It creates a domain to execute the code which is the routine body for setY. The code for setY is either mixed in with the code of main or placed in another segment, whatever is more convenient, probably mixed in. The new domain is somehow equipped with an address segment that includes the code for setY and the array. Its address segment may indeed be identical to that of main. A start key for the mew domain is created and sent to the key adj_up who may accomplish the array update with high assurance that it can have no other direct effect on the array.

Other constraints can be coded into setY such as requiring the new value to be even:

void setY(int x, int y){if(0<=x && x<100) array[x].Y = y & ~1;}

I have seen introductions to Relational Database systems propose the use of schema to protect properties of the database proper. Procedural abstraction is more powerful and depends only on ancient language features.

Other Languages

Scheme
(define (adj_up pr)
; ...
(pr 31 42)
; ...
)

(define (main) (let* (
  (array (make-vector 100 '(() . ())))
  (setY (lambda (x y) (set-cdr! (vector-ref array x) y))))
; ...
(adj_up setY)
; ...
(vector-ref array 33)))
or more briefly main might be
(define (main) (let* (
  (array (make-vector 100 '(() . ()))))
; ...
(adj_up (lambda (x y) (set-cdr! (vector-ref array x) y)))
; ...
(vector-ref array 33)))
I think that the Scheme platforms that I have used, make this protection very good. I have seen this pattern in SmallTalk.

Algol68

PROC adj_up = (PROC(INT, INT)VOID pr)VOID: BEGIN
# ... #
pr(31, 42)
# ... #
END;

MODE PAIR = STRUCT(INT x, INT y);
PROC main = INT: BEGIN
   [100]PAIR array; FOR i TO 100 DO array[i] := (0, 0) OD;
   PROC sety = (INT x, y)VOID:
      (0<x ANDF x<= 100 | y OF array[x] := y);
   # ... #
   adj_up(sety);
   # ... #
   print(array[31]);
   0
END;
  main
“x OF y” in Algol68 means just what “y.x” means in later years. Algol had a slick way of solving this particular problem:
PROC adj_up = (REF []INT ap)VOID: BEGIN
# ... #
ap[31] := 42
# ... #
END;

MODE PAIR = STRUCT(INT x, INT y);
PROC main = INT: BEGIN
   [100]PAIR array; FOR i TO 100 DO array[i] := (0, 0) OD;
   # ... #
   adj_up(y OF array);
   # ... #
   print(array[31]);
   0
END;
  main
Where array is a reference to an array of pairs, y OF array (or array.y in modern notation) is a reference to an array of integers, with an 8 byte stride, most likely! The bounds checks will be done at runtime! These Algol68 tricks are limited to array constructs, however. They cannot require even y values, for instance.

OO languages make much of protecting data from their callers and callees. If the array in the above example is to be protected from all but one exterior program, adj_up in the example above, then there is a dilemma. If we add a method to the class that abstracts the array, where that method sets a Y value of the array, then any holder of an object reference to an instance of that class will be able to change the Y value where in the patterns above only the one routine with that need gets it. I have not included Java in these examples for Java does not admit procedure values. I would like to see how Java best addresses this particular protection pattern. There is a Java variant Pizza, that includes procedure values and Pizza can probably solve the problem like the above languages. The Pizza extensions seemed to be very natural and I think that no modifications were necessary to the byte code semantics. It would seem that the lack of procedures was philosophical. The Pizza solution would presumably be secure as well.


Of these languages, only Scheme and Algol68, contingent on the implementation, are secure. SmallTalk has enough mechanism to ensure safety but then provides more mechanism to access the caller’s stack in order to allow access for debuggers. Algol68 did not require the implementation to check for references to departed stack frames, but some implementations did.