Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 2020_gitignore_updates
  • history
  • master
  • squashed
  • tourney_patches
5 results

Target

Select target project
No results found
Select Git revision
  • history
  • master
  • squashed
  • tourney_patches
4 results
Show changes

Commits on Source 1

23 files
+ 3903
0
Compare changes
  • Side-by-side
  • Inline

Files

.gitlab-ci.yml

0 → 100644
+45 −0
Original line number Original line Diff line number Diff line
stages:
    - check_elig
    - validate_tests
    - validate_progs
    - submit

check_submitter_eligibility:
    stage: check_elig
    only:
        - master
    tags:
        - "swen90006-tourney"
    script:
        - python3 ~/swen_tourney/frontend.py check_eligibility $(realpath $(pwd))
    allow_failure: false

validate_tests:
    stage: validate_tests
    only:
        - master
    tags:
        - "swen90006-tourney"
    script:
        - python3 ~/swen_tourney/frontend.py validate_tests $(realpath $(pwd))
    allow_failure: false

validate_progs:
    stage: validate_progs
    only:
        - master
    tags:
        - "swen90006-tourney"
    script:
        - python3 ~/swen_tourney/frontend.py validate_progs $(realpath $(pwd))
    allow_failure: false

submit:
    stage: submit
    only:
        - master
    tags:
        - "swen90006-tourney"
    script:
        - python3 ~/swen_tourney/frontend.py submit $(realpath $(pwd))
    allow_failure: false

Makefile

0 → 100644
+58 −0
Original line number Original line Diff line number Diff line
BINARY=passbook

SRC=src
BIN=bin
INCLUDE=$(SRC)/include

VERSIONS ?= original vuln-1 vuln-2 vuln-3 vuln-4 vuln-5

VBINARIES=$(VERSIONS:%=%/$(BINARY))
BIN_TARGETS=$(VBINARIES) $(VBINARIES:%=%-san) $(VBINARIES:%=%-fuzz) $(VBINARIES:%=%-cov)

BIN_DIRS=$(BIN) $(VERSIONS:%=$(BIN)/%)

TARGETS=$(BIN_TARGETS:%=$(BIN)/%)

HEADERS=$(wildcard $(INCLUDE)/*.h)

# allow the user to provide additional CFLAGS by doing e.g. CFLAGS=blah make
CFLAGS += -W -Wall -Wpedantic -Wno-language-extension-token -g -I$(INCLUDE)

# allow the user to override what clang we use by doing e.g. CLANG=blah make
CLANG ?= clang-6.0
CC=$(CLANG)

SAN_FLAGS ?= -fsanitize=address -fno-omit-frame-pointer -DPASSBOOK_FUZZ
FUZZ_FLAGS ?= -DPASSBOOK_LIBFUZZER -fsanitize=fuzzer,address -fno-omit-frame-pointer -DPASSBOOK_FUZZ
NO_STRICT_OVERFLOW_CFLAGS ?= -fwrapv -fno-strict-overflow -Wstrict-overflow
COV_FLAGS ?= -fprofile-instr-generate -fcoverage-mapping -DPASSBOOK_FUZZ

default: $(BIN_DIRS) $(TARGETS)

$(BIN_DIRS):
	mkdir -p $@

.PHONY: default


$(BIN)/%: $(SRC)/%.c $(HEADERS)
	$(CC) $< $(CFLAGS) $(LDFLAGS) $(NO_STRICT_OVERFLOW_CFLAGS) -o $@ 


$(BIN)/%-san: $(SRC)/%.c $(HEADERS)
	$(CLANG) $< $(CFLAGS) $(LDFLAGS) $(SAN_FLAGS) $(NO_STRICT_OVERFLOW_CFLAGS) -o $@


# build a self-fuzzing binary with libFuzzer
# needs a recent clang version (e.g. clang-6.0)
# then to run it:
# ./passbook-fuzz ~/tmp/corpus/ ~/tmp/findings/ -timeout=5 -only-ascii=1 -dict=libfuzzer-dict -max_total_time=1200 -print_final_stats=1
$(BIN)/%-fuzz: $(SRC)/%.c $(HEADERS)
	$(CLANG) $< $(CFLAGS) $(LDFLAGS) $(FUZZ_FLAGS) $(NO_STRICT_OVERFLOW_CFLAGS) -o $@

$(BIN)/%-cov: $(SRC)/%.c $(HEADERS)
	$(CLANG) $< $(CFLAGS) $(LDFLAGS) $(COV_FLAGS) $(NO_STRICT_OVERFLOW_CFLAGS) -o $@

clean:
	rm -rf bin/* *.profraw *.profdata

README.md

0 → 100644
+26 −0
Original line number Original line Diff line number Diff line
# SWEN90006 Assignment 2 2019

Please see the assignment handout which contains all the essential
information.

Structure of this repository:

* src/original/: -  where the code for the original passbook lives
* src/vuln-1 -- src/vuln-5 - where your vulnerable versions will live
* poc/:        -  where your PoCs will live
* fuzzer/:     -  where your fuzzer will live

Pre-Included Scripts:

* Makefile         - makefile for building the C implementation etc.
* get_coverage.sh  - script to generate coverage reports
* run_fuzzer.sh    - script for running your fuzzer to generate inputs 

Vulnerable Versions (you should put your security vulnerabilities in here):

* src/vuln-1/passbook.c -- src/vuln-5/passbook.c

Proofs of Concept (PoCs that you should provide for each vulnerability):

* poc/vuln-1.poc -- poc/vuln-5.poc

build.sh

0 → 100644
+5 −0
Original line number Original line Diff line number Diff line
#!/bin/sh

source eng_unimelb_setup

CFLAGS=-DNDEBUG make

eng_unimelb_setup

0 → 100755
+16 −0
Original line number Original line Diff line number Diff line
#!/bin/sh

# setup environment variables for dimefox.eng.unimelb.edu.au
#  and related servers

LIBDIR="/silo-q04/users/t/tobiasm1/local/lib64"

RHEL=$(cat /etc/issue | grep -F "Red Hat Enterprise Linux Server release 6.10 (Santiago)")

if [ ! -z "${RHEL}" ]
then
    export LD_LIBRARY_PATH=$LIBDIR
    export LDFLAGS=-L$LIBDIR
fi
    
 No newline at end of file

fuzzer/Fuzzer.java

0 → 100644
+38 −0
Original line number Original line Diff line number Diff line
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.PrintWriter;


/* a stub for your team's fuzzer */
public class Fuzzer {

    private static final String OUTPUT_FILE = "fuzz.txt";
    
    public static void main(String[] args) throws IOException {
        System.out.println(Instruction.getBNF());
        FileOutputStream out = null;
        PrintWriter pw = null;
        try {
            out = new FileOutputStream(OUTPUT_FILE);
            pw = new PrintWriter(out);
            
            /* We just print one instruction.
               Hint: you might want to make use of the instruction
               grammar which is effectively encoded in Instruction.java */
            pw.println("list");
            
        }catch (Exception e){
            e.printStackTrace(System.err);
            System.exit(1);
        }finally{
            if (pw != null){
                pw.flush();
            }
            if (out != null){
                out.close();
            }
        }

    }

}
+56 −0
Original line number Original line Diff line number Diff line
import java.util.Arrays;
import java.util.ArrayList;

public enum Instruction {
    PUT("put",new OperandType[]{OperandType.STRING,OperandType.STRING,OperandType.STRING}),
    GET("get",new OperandType[]{OperandType.STRING}),
    REM("rem",new OperandType[]{OperandType.STRING}),
    SAVE("save",new OperandType[]{OperandType.STRING,OperandType.STRING}),
    LIST("list",new OperandType[]{}),
    MASTERPW("masterpw",new OperandType[]{OperandType.STRING});

    public static String getBNF(){
        String grammar = "<INSTRUCTION> ::= \n";
        Instruction[] INSTS = Instruction.values();
        boolean firstInst = true;
        for (Instruction inst : INSTS){
            if (firstInst){
                grammar += "      \"";
                firstInst = false;
            }else{
                grammar += "    | \"";
            }
            grammar += inst.getOpcode() + "\"";
            for (OperandType op : inst.getOperands()){
                grammar += " <" + op.toString() + ">";
            }
            grammar += "\n";
        }
        return grammar;
    }
    
    private final String opcode;
    private final OperandType[] operands;

    Instruction(String opcode, OperandType[] operands){
        this.opcode = opcode;
        this.operands = operands;
    }

    public String getOpcode(){
        return opcode;
    }
    
    public OperandType[] getOperands(){
        return operands;
    }

    public String toString(){
        String operandsString = "";
        for (OperandType op : operands) {
            operandsString += " " + op.toString();
        }
        return "\"" + opcode + "\"" + operandsString;
    }
    
}
+3 −0
Original line number Original line Diff line number Diff line
public enum OperandType {
    STRING
}

get_coverage.sh

0 → 100755
+63 −0
Original line number Original line Diff line number Diff line
#!/bin/sh

if [ -z ${TOOL_SUFFIX+x} ]
then
    # TOOL_SUFFIX not set
    # try to intelligently set TOOL_SUFFIX
    case $(uname) in
        Linux)
            TOOL_SUFFIX=-6.0
            ;;
        Darwin)
            TOOL_SUFFIX=
            ;;
        *)
            TOOL_SUFFIX=-6.0
    esac
fi


CLANG=clang${TOOL_SUFFIX}
LLVM_PROFDATA=llvm-profdata${TOOL_SUFFIX}
LLVM_COV=llvm-cov${TOOL_SUFFIX}

if [ -z "$(which ${CLANG})" ]
then
    echo "${CLANG} doesn't exist. Try setting TOOL_SUFFIX environment variable"
    exit 1
fi

if [ -z "$(which ${LLVM_PROFDATA})" ]
then
    echo "${LLVM_PROFDATA} doesn't exist. Try setting TOOL_SUFFIX environment variable"
    exit 1
fi

if [ -z "$(which ${LLVM_COV})" ]
then
    echo "${LLVM_COV} doesn't exist. Try setting TOOL_SUFFIX environment variable"
    exit 1
fi

echo "using ${CLANG}, ${LLVM_PROFDATA} and ${LLVM_COV}"

export LLVM_PROFILE_FILE="passbook-%m.profraw"

if [ $# -lt 1 ]
then
    echo "Usage: $0 inputfile1 [inputfile2 ...]"
    exit 1
fi


rm -f passbook*.profraw passbook.profdata
echo "First re-building to make sure -DNDEBUG is turned on..."
BINARY=./bin/original/passbook-cov
rm -f ${BINARY}
CLANG=${CLANG} CFLAGS="-DNDEBUG ${CFLAGS}" make ${BINARY}

${BINARY} $*

${LLVM_PROFDATA} merge -sparse passbook*.profraw -o passbook.profdata
${LLVM_COV} show ${BINARY} -instr-profile=passbook.profdata
${LLVM_COV} report ${BINARY} -instr-profile=passbook.profdata

poc/vuln-1.poc

0 → 100644
+0 −0
Original line number Original line Diff line number Diff line

poc/vuln-2.poc

0 → 100644
+0 −0
Original line number Original line Diff line number Diff line

poc/vuln-3.poc

0 → 100644
+0 −0
Original line number Original line Diff line number Diff line

poc/vuln-4.poc

0 → 100644
+0 −0
Original line number Original line Diff line number Diff line

poc/vuln-5.poc

0 → 100644
+0 −0
Original line number Original line Diff line number Diff line

run_fuzzer.sh

0 → 100755
+27 −0
Original line number Original line Diff line number Diff line
#!/bin/sh

if [ ! -d fuzzer/ ]; then
   echo "fuzzer/ directory not found here!"
   exit 1
fi
   
if [ ! -d tests/ ]; then
    mkdir -p tests/
fi

cd fuzzer/
javac Fuzzer.java

NUMFUZZ=100
FUZZER_OUTPUT=fuzz.txt
i=0;
while [ $i -lt $NUMFUZZ ]; do
      rm -f ${FUZZER_OUTPUT}
      java Fuzzer
      if [ ! -f ${FUZZER_OUTPUT} ]; then
          echo "Fuzzer did not produce output file ${FUZZER_OUTPUT}"
          exit 1
      fi
      mv ${FUZZER_OUTPUT} ../tests/fuzz-$i.txt
      let i=$i+1
done

run_tests.sh

0 → 100755
+14 −0
Original line number Original line Diff line number Diff line
#!/bin/sh

PROGRAM=$1
USE_POC=$2

if [ "$USE_POC" = "--use-poc" ]; then
	# test program with its proof of concept test
	./bin/${PROGRAM}/passbook-san ./poc/${PROGRAM}.poc
	exit $?
else
	# fuzz program with all tests in the tests/ folder
	./bin/${PROGRAM}/passbook-san ./tests/*
	exit $?
fi

src/include/debug.h

0 → 100644
+12 −0
Original line number Original line Diff line number Diff line
#ifndef _DEBUG_H_
#define _DEBUG_H_


#ifndef NDEBUG
#include <stdio.h>
#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_printf(...)
#endif

#endif
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif

src/vuln-1/passbook.c

0 → 100644
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif

src/vuln-2/passbook.c

0 → 100644
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif

src/vuln-3/passbook.c

0 → 100644
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif

src/vuln-4/passbook.c

0 → 100644
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif

src/vuln-5/passbook.c

0 → 100644
+590 −0
Original line number Original line Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <pwd.h>
#include <unistd.h>

#include "debug.h"

#ifdef PASSBOOK_LIBFUZZER
#include <stdint.h>
const char LIBFUZZER_INPUT_FILE[] = "libFuzzerInput.tmp";
/* turn off tracing to make it run faster */
#define printf(...)
#define fprintf(...)
#endif

const char INSTRUCTION_PUT[] = "put";

const char INSTRUCTION_REM[] = "rem";

const char INSTRUCTION_GET[] = "get";

const char INSTRUCTION_SAVE[] = "save";

const char INSTRUCTION_LIST[] = "list";

const char INSTRUCTION_MASTERPW[] = "masterpw";

/* a credential is a username/password pair */
typedef struct {
  char * username;
  char * password;
} cred_t;

/* we store a mapping from URLs to credentials using a binary tree
   to try to ensure log lookup performance */
typedef struct node {
  char * url;
  cred_t cred;
  struct node *left;
  struct node *right;
} node_t;

static const node_t * lookup(const node_t *p, const char *url){
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      return p;
    }else if (ret < 0){
      p = p->left;
    }else{
      p = p->right;
    }
  }
  return p; // not found
}

static void node_print(const node_t *p){
  printf("URL: %s, Username: %s, Password: %s\n",p->url,p->cred.username,p->cred.password);
}

/* construct a new node */
static node_t *node_new(const char *url, const cred_t cred){
  node_t *new = malloc(sizeof(node_t));
  assert(new != NULL && "new: malloc failed");
  new->url = strdup(url);
  assert(new->url != NULL && "new: strdup url failed");
  new->cred.username = strdup(cred.username);
  assert(new->cred.username != NULL && "new: strdup username failed");  
  new->cred.password = strdup(cred.password);
  assert(new->cred.password != NULL && "new: strdup password failed");
  new->left = NULL;
  new->right = NULL;
  return new;
}

/* updates a node's credential in place: 
   replaces p's credential with that from q and frees q */
static void node_edit_cred(node_t * p, node_t *q){
  free(p->cred.username);
  free(p->cred.password);

  p->cred.username = q->cred.username;
  p->cred.password = q->cred.password;
  free(q->url);
  free(q);
}

static void node_free(node_t *p){
  free(p->url);
  free(p->cred.username);
  free(p->cred.password);
  free(p);
}

/* insert q into p
   we assume that if q has children then it cannot already
   be present in p. Otherwise, if q has no children and we find its url in p,
   then we edit the found entry in place while preserving its children */
static node_t * node_insert(node_t *p, node_t *q){
  if (p == NULL){
    return q;
  }
  if (q == NULL){
    return p;
  }
  /* we store a pointer to a node pointer that remembers where in the 
     tree the new node needs to be added */
  node_t ** new = NULL;
  node_t * const start = p;
  while (new == NULL) {
    int ret = strcmp(q->url,p->url);
    if (ret == 0){
      assert (q->left == NULL && q->right == NULL && "illegal insertion");
      /* edit the node in place */
      node_edit_cred(p,q);
      /* q is now freed so cannot be used anymore */
      return start;
    }else if (ret < 0){
      if (p->left == NULL){
        new = &(p->left);
      }else{
        p = p->left;
      }
    }else{
      if (p->right == NULL){
        new = &(p->right);
      }else{
        p = p->right;
      }
    }
  }
  *new = q;
  return start;
}

/* returns a pointer to the tree with the node added or with the existing
   node updated if it was  already present */
static node_t * put(node_t *p, const char *url, const cred_t cred){
  return node_insert(p,node_new(url,cred));
}

/* destroy tree rooted at p */
static void destroy(node_t *p){
  while (p != NULL){
    node_t * left = p->left;
    node_t * const right = p->right;
    left = node_insert(left,right);
    node_free(p);
    p = left;
  }
}

/* returns a pointer to the tree with the node removed (if it was present) */
static node_t * rem(node_t *p, const char *url){
  node_t * const start = p;
  /* remember where the pointer to p was stored */
  node_t ** pptr = NULL;
  while (p != NULL){
    int ret = strcmp(url,p->url);
    if (ret == 0){
      node_t * left = p->left;
      node_t * const right = p->right;
      left = node_insert(left,right);
      node_free(p);
      if (pptr != NULL){
        *pptr = left;
        return start;
      }else{
        /* p was the only node in the tree */
        assert(p == start);
        return left;
      }
    }else if (ret < 0){
      pptr = &(p->left);
      p = p->left;
    }else{
      pptr = &(p->right);
      p = p->right;
    }
  }
  return start; // not found
}

const char WHITESPACE[] = " \t\r\n";


/* tokenise a string, splitting on characters in WHITESPACE, up to
 * a maxium of toksLen tokens, each of whose start addresses is put into
 * toks and each of which is NUL-terminated in str.
 * returns number of tokens found */
unsigned int tokenise(char *str, char * toks[], unsigned int toksLen){
  unsigned numToks = 0;
  while (numToks < toksLen){
    /* strip leading whitespace */     
    size_t start = strspn(str,WHITESPACE);
    if (str[start] != '\0'){
      toks[numToks] = &(str[start]);    
    
      /* compute the length of the token */
      const size_t tokLen = strcspn(toks[numToks],WHITESPACE);
      if (tokLen > 0){
        toks[numToks][tokLen] = '\0';
        str = &(toks[numToks][tokLen+1]);
        numToks++;
      }else{
        return numToks;
      }
    }else{
      return numToks;
    }
  }
  return numToks;
}

#define MAX_LINE_LENGTH  1022
#define MAX_INSTRUCTIONS 1024
/* two extra chars in each line: the newline '\n' and NUL '\0' */
#define INSTRUCTION_LENGTH (MAX_LINE_LENGTH+2)


/* a global instruction buffer */
char inst[INSTRUCTION_LENGTH];

/* password mapping for each url: initially empty */
node_t * map = NULL;

/* a doubly-linked list of node pointers
   is used to implement stacks/queues of nodes so we can implement various
   tree traversal algorithms without using recursion (to avoid stack overflow
   for very large trees). Stack overflow is a trivial form of memory-safety
   vulnerability. */
typedef struct nodeptr_list_elem {
  const node_t *p;
  struct nodeptr_list_elem *next;
  struct nodeptr_list_elem *prev;
} nodeptr_list_elem_t;

typedef struct nodeptr_list {
  nodeptr_list_elem_t *head;
  nodeptr_list_elem_t *last;
} nodeptr_list_t;


/* push an element p onto the front of a nodeptr list lst */
nodeptr_list_t list_push(nodeptr_list_t lst, const node_t *p){
  nodeptr_list_elem_t *n = malloc(sizeof(nodeptr_list_elem_t));
  assert(n != NULL && "push: malloc failed");
  n->p = p;
  n->next = lst.head;
  n->prev = NULL;  
  if (lst.head != NULL){
    assert(lst.last != NULL);
    lst.head->prev = n;
  }else{
    assert(lst.last == NULL);
    lst.last = n;
  }
  lst.head = n;
  
  return lst;
}

/* when out is non-NULL we place a pointer to the first node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_pop(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.head->p;
  }
  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.head->next;
    free(lst.head);
    lst.head = ret;
  }
  return lst;
}

/* when out is non-NULL we place a pointer to the last node into it.
   assumption: lst.head and lst.last are non-NULL */
nodeptr_list_t list_dequeue(nodeptr_list_t lst, const node_t **out){
  assert(lst.head != NULL && lst.last != NULL);
  if (out != NULL){
    *out = lst.last->p;
  }

  if (lst.last == lst.head){
    free(lst.head);
    lst.head = NULL;
    lst.last = NULL;
  }else{
    nodeptr_list_elem_t *ret = lst.last->prev;
    free(lst.last);
    lst.last = ret;
  }
  return lst;
}

/* in order traversal to print out nodes in sorted order. Is used to
   implement listing of all entries in the passbook */
void print_inorder(const node_t *p){
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.head != NULL){
      // keep recursing left until we can go no further
      while (p->left != NULL){
        lst = list_push(lst,p->left);
        p = p->left;
      }
      
      // pop from the stack to simulate the return
      const node_t *q;
      lst = list_pop(lst,&q);

      // print the node following the return
      node_print(q);

      // simulate right recursive call
      if (q->right != NULL){
        lst = list_push(lst,q->right);
        p = q->right;
      }
    }
  }
}

/* save a node to the given file. We save to the file a "put" instruction
   that will cause the node to be placed back into the passbook when the
   file is read. */
void node_save(const node_t *p, FILE *f){
  fprintf(f,"%s",INSTRUCTION_PUT);
  fprintf(f," ");
  fprintf(f,"%s",p->url);
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.username);  
  fprintf(f," ");  
  fprintf(f,"%s",p->cred.password);
  fprintf(f,"\n");
}

/* save the master password to the given file. We save a "masterpw" 
   instruction that will cause the passbook to prompt the user for the
   given master password the next time the file is read */
void masterpw_save(const char *pw, FILE *f){
  fprintf(f,"%s",INSTRUCTION_MASTERPW);
  fprintf(f," ");
  fprintf(f,"%s",pw);
  fprintf(f,"\n");
}

/* level order (i.e. breadth-first) traversal to print nodes out in the
   order that they need to be put back in to an empty tree to ensure
   that the resulting tree has the same structure as the original one.
   This is how we save the passbook to a file.
   Returns 0 on success; nonzero on failure */
int save_levelorder(const node_t *p, const char *masterpw,
                    const char * filename){
#ifdef PASSBOOK_FUZZ
  // ignore the file name when fuzzing etc. to avoid DoS on the server
  FILE *f = fopen("/dev/null","w");
#else
  FILE *f = fopen(filename,"w");
#endif
  if (f == NULL){
    fprintf(stderr,"Couldn't open file %s for writing.\n",filename);
    return -1;
  }
  masterpw_save(masterpw,f);
  nodeptr_list_t lst = {.head = NULL, .last = NULL};
  if (p != NULL){
    lst = list_push(lst,p);

    while(lst.last != NULL){
      lst = list_dequeue(lst,&p);
      node_save(p,f);
      if (p->left != NULL){
        lst = list_push(lst,p->left);
      }
      if (p->right != NULL){
        lst = list_push(lst,p->right);
      }
    }
  }
  fclose(f);
  return 0;
}

/* returns 0 on successful execution of the instruction in inst */
static int execute(void){
  char * toks[4]; /* these are pointers to start of different tokens */
  const unsigned int numToks = tokenise(inst,toks,4);
    
  if (numToks == 0){
    /* blank line */
    return 0;
  }
    
  if (strcmp(toks[0],INSTRUCTION_GET) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_GET,numToks-1);
      return -1;
    }
    debug_printf("Looking up: %s\n",toks[1]);
    const node_t *p = lookup(map,toks[1]);
    if (p != NULL){
      node_print(p);
    }else{
      printf("Not found.\n");
    }

  } else if (strcmp(toks[0],INSTRUCTION_REM) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_REM,numToks-1);
      return -1;
    }
    debug_printf("Removing: %s\n",toks[1]);
    map = rem(map,toks[1]);
    
  } else if (strcmp(toks[0],INSTRUCTION_PUT) == 0){
    if (numToks != 4){
      debug_printf("Expected 3 arguments to %s instruction but instead found %u\n",INSTRUCTION_PUT,numToks-1);
      return -1;
    }
    cred_t cred;
    cred.username = toks[2];
    cred.password = toks[3];
    map = put(map,toks[1],cred);

  } else if (strcmp(toks[0],INSTRUCTION_SAVE) == 0){
    if (numToks != 3){
      debug_printf("Expected 2 arguments to %s instruction but instead found %u\n",INSTRUCTION_SAVE,numToks-1);
      return -1;
    }
    debug_printf("Saving under master password %s to file: %s\n",toks[1],toks[2]);
    if (save_levelorder(map,toks[1],toks[2]) != 0){
      debug_printf("Error saving to file %s\n",toks[2]);
      return -1;
    }

  } else if (strcmp(toks[0],INSTRUCTION_MASTERPW) == 0){
    if (numToks != 2){
      debug_printf("Expected 1 argument to %s instruction but instead found %u\n",INSTRUCTION_MASTERPW,numToks-1);      return -1;
    }
    // when fuzzing (or gathering coverage stats, etc.) don't check master pw
#ifndef PASSBOOK_FUZZ
    const char * pass = getpass("Enter master password: ");
    if (pass == NULL || strcmp(pass,toks[1]) != 0){
      fprintf(stderr,"Master password incorrect!\n");
      exit(1); // exit immediately
    }
#else
    return -1; 
#endif    

  } else if (strcmp(toks[0],INSTRUCTION_LIST) == 0){
    if (numToks != 1){
      debug_printf("Expected 0 arguments to %s instruction but instead found %u\n",INSTRUCTION_LIST,numToks-1);
      return -1;
    }
    print_inorder(map);

  }else{
    debug_printf("Unrecognised instruction %s\n",toks[0]);
    return -1;
  }
  
  return 0;
}

/* returns >=0 on success, in which case the number of instructions executed
   is returned. Returns < 0 on failure. */
static int run(FILE *f){
  assert(f != NULL);
  
  int instructionCount = 0;
  while (instructionCount < MAX_INSTRUCTIONS){
    memset(inst,0,sizeof(inst));
    char * res = fgets(inst,sizeof(inst),f);
    if (res == NULL){
      if (feof(f)){
        /* end of file */
        return instructionCount;
      }else{
        debug_printf("Error while reading, having read %d lines\n",instructionCount);
        return -1;
      }
    }
    if (inst[MAX_LINE_LENGTH] != '\0'){
      if (!(inst[MAX_LINE_LENGTH] == '\n' && inst[MAX_LINE_LENGTH+1] == '\0')){
        fprintf(stderr,"Line %d exceeds maximum length (%d)\n",instructionCount+1,MAX_LINE_LENGTH);
        debug_printf("(Expected at array index %d to find NUL but found '%c' (%d))\n",MAX_LINE_LENGTH,inst[MAX_LINE_LENGTH],inst[MAX_LINE_LENGTH]);
        return -1;
      }
    }else{
      /* inst[MAX_LINE_LENGTH] == '\0', so
         strlen is guaranteed to be <= MAX_LINE_LENGTH
         Check if it has a newline and add it if it needs it */
      size_t len = strlen(inst);
      if (len > 0){
        if (inst[len-1] != '\n'){
          inst[len] = '\n';
          inst[len+1] = '\0';
        }
      }
    }
    instructionCount++;
    int r = execute();
    if (r != 0){
      return -1;
    }
  }

  if (feof(f)){
    /* final line of file didn't have a trailing newline */
    return instructionCount;
  }else{
    /* see if we are at end of file by trying to do one more read.
       this is necessary if the final line of the file ends in a 
       newline '\n' character */
    char c;
    int res = fread(&c,1,1,f);
    if (res == 1){
      fprintf(stderr,"Number of instructions (lines) in file exceeds max (%d)\n",MAX_INSTRUCTIONS);
      return -1;
    }else{
      if (feof(f)){
        /* final read found the EOF, so all good */
        return instructionCount;
      }else{
        /* probably won't ever get here */
        debug_printf("Error while trying to test if line %d was empty\n",instructionCount+1);
        return -1;
      }
    }
  }
}

#ifdef PASSBOOK_LIBFUZZER
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FILE *f = fopen(LIBFUZZER_INPUT_FILE,"w");
  fwrite(Data,Size,1,f);
  fclose(f);
  f = fopen(LIBFUZZER_INPUT_FILE,"r");
  run(f);
  fclose(f);
  destroy(map);
  map = NULL;
  return 0; /* libFuzzer wants 0 returned always */
}
#else
int main(const int argc, const char * argv[]){
  if (argc <= 1){
    fprintf(stderr,"Usage: %s file1 file2 ...\n",argv[0]);
    fprintf(stderr,"       use - to read from standard input\n");
    exit(0);
  }
  
  for (int i = 1; i<argc; i++){
    printf("Running on input file %s\n",argv[i]);
    FILE *f;
    if (strcmp(argv[i],"-") == 0){
      f = stdin;
    }else{
      f = fopen(argv[i],"r");
      if (f == NULL){
        fprintf(stderr,"Error opening %s for reading\n",argv[i]);
        destroy(map);
        exit(1);
      }
    }
    int ans = run(f);
    if (ans < 0){
      fprintf(stderr,"Error\n");
    }
    /* do not close stdin */
    if (f != stdin){
      fclose(f);
    }
  }
  destroy(map);
  return 0;
}
#endif