CSC447

Concepts of Programming Languages

Safety

Instructor: James Riely

C type

  • What do you make of this program?

$ clang -m32 typing-00.c && ./a.out
(float)*p = 2123456768.000000
*(float*)p = 96621069057346178268049192388430659584.000000
i*s = -1047484
          

int main() {
    int *p = (int*) malloc (sizeof(int));
    *p = 2123456789;
    
    printf ("(float)*p = %f\n", (float)*p);   /* loss of precision */
    printf ("*(float*)p = %f\n", *(float*)p); /* rubbish */

    int i = 2;
    char s[] = "three";
    
    printf ("i*s = %ld\n", i*(long)s); 
}

Unsafe access

  • Memory location contains data written at a given type (such as character array)
  • The same memory location is read without permission or interpreted at an incompatible type (such as int)
  • This is an unsafe access
  • Scheme prevents unsafe access by throwing an exception
  • 
    #;> (+ "dog" 1)
    Error in +: expected type number, got '"dog"'.
                

Array bounds


$ clang -m32 typing-03.c && ./a.out
f=10.000000, a[0]=10 i=10
f=10.000000, a[0]=10 i=32401
f=96621069057346178268049192388430659584.000000, a[0]=10 i=32401
          

int main () {
    float f = 10;    
    int a[] = { 10 };
    short i = 10;
                        printf ("f=%f, a[0]=%d i=%d\n", f, a[0], i);
    a[-1] = 2123456789; printf ("f=%f, a[0]=%d i=%d\n", f, a[0], i);
    a[1]  = 2123456789; printf ("f=%f, a[0]=%d i=%d\n", f, a[0], i);
}
          

Aliasing a pointer on the Stack


$ gcc typing-06.c && ./a.out
x=2123456789, y=2123456789.000000
x=2123456789, z=38685644468023060038942720.000000
          

int main() {
    int x = 2123456789;
    double y = x;
    printf ("x=%d, y=%f\n", x, y);
    double *p = &x;
    double z = *p;
    printf ("x=%d, z=%f\n", x, z);
}
          

Aliasing a pointer on the Heap


$ gcc typing-07.c && ./a.out
*fp=10.000000, *ip=1092616192
 fp=0x1063010,  ip=0x1063010
          

int main() {
    int* ip = (int*) malloc (sizeof(int));
    *ip = 10;
    free(ip);
    float* fp = (float*) malloc (sizeof(float));
    *fp = 10;
    printf ("*fp=%f, *ip=%d\n", *fp, *ip);
    printf (" fp=%p,  ip=%p\n", fp, ip);
}
          

Function pointers


$ clang -m32 typing-04.c && ./a.out
hurray!
ouch!
          

void unsafeCommand () { printf ("ouch!\n"); }
void safeCommand ()   { printf ("hurray!\n"); }
int main () {
    int diff = &unsafeCommand - &safeCommand;
    void (*c) () = &safeCommand;
    c();
    c += diff;
    c();
}
          

Function pointers with arguments


$ clang -m32 typing-05.c && ./a.out
i=2123456789
f=96621069057346178268049192388430659584.000000
          

void floatCommand (float f) { printf ("f=%f\n", f); }
void intCommand (int i)     { printf ("i=%d\n", i); }
int main () {
    int diff = (void*)&intCommand - (void*)&floatCommand;
    void (*c) (int) = &intCommand;
    int j = 2123456789;
    c(j);
    c -= diff;
    c(j);
}
          

Unsafety and Security

Java

To be safe, Java does the following

  • Disallows pointers into the stack
  • Disallows pointer arithmetic
  • Disallows explicit free
  • Checks array bounds
  • Checks potentially unsafe casts

Bounds checking


$ javac Typing04.java && java Typing04
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
          

class Typing04 {
  public static void main (String[] args) {
    Object[] bs = new Object[4];
    Object b = bs[-1];
  }  
}
          

Checked casts


$ javac Typing05.java && java Typing05
Exception in thread "main" java.lang.ClassCastException: C cannot be cast to B
          
              
class A { int x; }
class B extends A { float y; }
class C extends A { char c; }

class Typing05 {
  static void f (B b) {
    A a = b;       /* upcast always safe */
  }
  static void g (A a) {
    B b = (B) a;   /* downcast must be checked */
  }
  public static void main (String[] args) {
      f (new B());
      g (new C());
  }  
}
          

Compare with dynamic cast in C++

Checked array assignment


$ javac Typing03.java && java Typing03
Exception in thread "main" java.lang.ArrayStoreException: C
          

class A { int x; }
class B extends A { float y; }
class C extends A { char c; }

class Typing03 {
  public static void main (String[] args) {
      B[] bs = new B[1];
      A[] as = bs;
      as[0] = new C(); /* write must be checked */
      B b = bs[0];     /* reading always safe */
  }
}
          

This is a design flaw -- More later

Safety

  • Traditional systems languages are purposefully unsafe
    • Assembly, C, C++, Objective-C, C#-unmanaged, ...
  • Recent application languages are meant to be safe
    • Java, Scheme, Javascript, Python, , C#-managed, ...
  • Recent systems languages attempt to isolate the unsafe bits
    • Rust, Go

Safety and Dynamism

  • Even in safe languages, the overuse of dynamic checks allows program flaws to make it into production
  • Overuse of null is considered a billion dollar mistake
  • Which is better?
    • Security hole due to lack of null checks
    • Program crashes on null pointer reference
  • No good options?