Add TD7
This commit is contained in:
parent
4296f3a394
commit
553cdc440c
24 changed files with 919 additions and 0 deletions
|
@ -0,0 +1,22 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $O $A $L $U $T
|
|
@ -0,0 +1,10 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
char buf[12];
|
||||
getpw(0, buf);
|
||||
printf("%s\n", buf);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
char name[] = "/tmp/mytest-XXXXXX";
|
||||
int ret;
|
||||
|
||||
ret = mktemp(name);
|
||||
if (ret < 0)
|
||||
perror("mktemp");
|
||||
ret = open(name, O_RDWR|O_CREAT|O_TRUNC, 0600);
|
||||
if (ret < 0)
|
||||
perror("open");
|
||||
else
|
||||
printf("ok\n");
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $O $A $L $U $T
|
|
@ -0,0 +1,24 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static char *f(void) {
|
||||
char *c = malloc(10);
|
||||
char *d = malloc(20);
|
||||
c[0] = 0;
|
||||
d[0] = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void g(void) {
|
||||
char *e = f();
|
||||
printf("%p\n", e);
|
||||
}
|
||||
|
||||
static void h(void) {
|
||||
g();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
h();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
char *c;
|
||||
|
||||
int main(void) {
|
||||
c = malloc(10);
|
||||
c[0] = 0;
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $O $A $L $U $T
|
|
@ -0,0 +1,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void f(char foo) {
|
||||
printf("foo is %d\n", foo);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *c = malloc(10);
|
||||
f(c[0]);
|
||||
free(c);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void f(char foo) {
|
||||
if (foo == 0)
|
||||
printf("foo is 0\n");
|
||||
else
|
||||
printf("foo is %d\n", foo);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *c = malloc(10);
|
||||
f(c[0]);
|
||||
free(c);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
|
||||
pframe:
|
||||
curl -o pframe.tgz https://dept-info.labri.fr/~thibault/SecuLog/pframe.tgz && \
|
||||
tar -xf pframe.tgz &&\
|
||||
rm -rf pframe.tgz
|
||||
|
||||
.gdbinit:
|
||||
|
||||
configure: pframe .gdbinit
|
||||
$(shell echo "python import pframe" > .gdbinit)
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
PHONY: gdb_% configure
|
||||
gdb_%: $(subst gdb_,,%)
|
||||
PYTHONPATH=${PWD}/pframe${PYTHONPATH:+:${PYTHONPATH}} setarch -R gdb $<
|
||||
|
||||
clean:
|
||||
rm -f $O $A $L $U $T
|
|
@ -0,0 +1,12 @@
|
|||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
int main(void) {
|
||||
int i = INT_MAX;
|
||||
|
||||
i++;
|
||||
|
||||
printf("%d\n", i);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
python import pframe
|
|
@ -0,0 +1,38 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
pframe:
|
||||
curl -o pframe.tgz https://dept-info.labri.fr/~thibault/SecuLog/pframe.tgz && \
|
||||
tar -xf pframe.tgz &&\
|
||||
rm -rf pframe.tgz
|
||||
|
||||
.gdbinit:
|
||||
|
||||
configure: pframe .gdbinit
|
||||
$(shell echo "python import pframe" > .gdbinit)
|
||||
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
PHONY: gdb_% configure
|
||||
gdb_%: $(subst gdb_,,%)
|
||||
PYTHONPATH=${PWD}/pframe${PYTHONPATH:+:${PYTHONPATH}} gdb $<
|
||||
|
||||
|
||||
PHONY: clean
|
||||
clean:
|
||||
rm -f $O $A $L $U $T pframe .gdbinit
|
|
@ -0,0 +1,9 @@
|
|||
Once unpacked this to, e.g. $HOME,
|
||||
|
||||
- Add this to .bashrc:
|
||||
|
||||
export PYTHONPATH=$HOME/pframe${PYTHONPATH:+:${PYTHONPATH}}
|
||||
|
||||
- Add this to .gdbinit:
|
||||
|
||||
python import pframe
|
|
@ -0,0 +1 @@
|
|||
__all__ = [ "pframe" ]
|
Binary file not shown.
|
@ -0,0 +1,93 @@
|
|||
import gdb
|
||||
|
||||
class PrintFrame (gdb.Command):
|
||||
|
||||
def __init__ (self):
|
||||
super (PrintFrame, self).__init__ ("pframe", gdb.COMMAND_STACK)
|
||||
|
||||
def syntax (self):
|
||||
print("Syntax: pframe[/nn][/-mm] where nn is the number of longs to be printed above sp (16 by default) and mm is the number of longs to be printed below sp (0 by default in 32bit, 16 by default in 64bit)")
|
||||
|
||||
def invoke (self, arg, from_tty):
|
||||
try:
|
||||
wordstar = gdb.lookup_type("unsigned long").pointer()
|
||||
wordsize = gdb.lookup_type("unsigned long").sizeof
|
||||
|
||||
nabove = 16
|
||||
if wordsize == 4:
|
||||
nbelow = 0
|
||||
else:
|
||||
nbelow = 16
|
||||
|
||||
if arg:
|
||||
l = arg.split('/')
|
||||
if l[0] != '':
|
||||
self.syntax()
|
||||
return
|
||||
for i in l[1:]:
|
||||
try:
|
||||
n = int(i)
|
||||
if n >= 0:
|
||||
nabove = n
|
||||
if n < 0:
|
||||
nbelow = -n
|
||||
except:
|
||||
self.syntax()
|
||||
return
|
||||
|
||||
frame = gdb.selected_frame()
|
||||
sp = frame.read_register('sp')
|
||||
if wordsize == 4:
|
||||
bp = frame.read_register('ebp')
|
||||
ip = frame.read_register('eip')
|
||||
else:
|
||||
bp = frame.read_register('rbp')
|
||||
ip = frame.read_register('rip')
|
||||
last = -nbelow*wordsize-1
|
||||
prevbp = bp.cast(wordstar).dereference()
|
||||
|
||||
if bp >= sp and bp < sp + 512:
|
||||
start = bp - sp + wordsize*8
|
||||
else:
|
||||
start = 0
|
||||
if start < wordsize*nabove:
|
||||
start = wordsize*nabove
|
||||
|
||||
for offset in range(start, last, -wordsize):
|
||||
addr = sp + offset
|
||||
s = '0x{:x}'.format(int(addr))
|
||||
|
||||
if ip >= addr and ip < addr + wordsize:
|
||||
s += ' ip'
|
||||
else:
|
||||
s += ' '
|
||||
|
||||
if addr == bp:
|
||||
s += ' bp'
|
||||
elif bp >= sp and addr == bp + wordsize:
|
||||
s += ' ret@'
|
||||
elif bp >= sp and addr == bp + 2*wordsize and (prevbp == 0 or addr < prevbp):
|
||||
s += ' arg1'
|
||||
elif bp >= sp and addr == bp + 3*wordsize and (prevbp == 0 or addr < prevbp):
|
||||
s += ' arg2'
|
||||
elif bp >= sp and addr == bp + 4*wordsize and (prevbp == 0 or addr < prevbp):
|
||||
s += ' arg3'
|
||||
elif bp >= sp and addr == bp + 5*wordsize and (prevbp == 0 or addr < prevbp):
|
||||
s += ' ... '
|
||||
else:
|
||||
s += ' '
|
||||
|
||||
if addr == sp:
|
||||
s += ' sp '
|
||||
else:
|
||||
s += ' '
|
||||
|
||||
val = int(addr.cast(wordstar).dereference())
|
||||
s += ('0x{:0'+str(wordsize*2)+'x}').format(val)
|
||||
print(s)
|
||||
|
||||
except gdb.error:
|
||||
print("Is the program running?")
|
||||
|
||||
PrintFrame ()
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
char c[10];
|
||||
|
||||
int main(void) {
|
||||
c[10] = 1;
|
||||
c[-1] = 1;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
char c[10];
|
||||
c[10] = 1;
|
||||
c[-1] = 1;
|
||||
|
||||
return c[10];
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
char *c = malloc(10);
|
||||
c[10] = 1;
|
||||
free(c);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
CFLAGS=-g -Wall -Wextra -Wno-unused-parameter
|
||||
LDLIBS=-lpthread
|
||||
C=$(wildcard *.c)
|
||||
O=$(C:.c=)
|
||||
A=$(C:.c=.asan)
|
||||
L=$(C:.c=.lsan)
|
||||
U=$(C:.c=.usan)
|
||||
T=$(C:.c=.tsan)
|
||||
|
||||
all: $O $A $U $T
|
||||
|
||||
%.asan: %.c
|
||||
$(CC) -fsanitize=address -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.lsan: %.c
|
||||
$(CC) -fsanitize=leak -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.usan: %.c
|
||||
$(CC) -fsanitize=undefined -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
%.tsan: %.c
|
||||
$(CC) -fsanitize=thread -fPIC $< -o $@ $(CFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $O $A $L $U $T
|
|
@ -0,0 +1,11 @@
|
|||
#include <stdio.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
argc--; // program name is not really an argument
|
||||
|
||||
// Extract bit 0 to check for parity of the number of arguments
|
||||
if ((argc & 1) == 0) {
|
||||
printf("even number of arguments\n");
|
||||
} else {
|
||||
printf("odd number of arguments\n");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#include <stdio.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
argc--; // program name is not really an argument
|
||||
|
||||
// Extract bit 0 to check for parity of the number of arguments
|
||||
if ((argc & 1) == 0) {
|
||||
printf("even number of arguments\n");
|
||||
} else {
|
||||
printf("odd number of arguments\n");
|
||||
}
|
||||
}
|
496
content/secu_logicielle/td7-analyse_statique_dynamique/index.md
Normal file
496
content/secu_logicielle/td7-analyse_statique_dynamique/index.md
Normal file
|
@ -0,0 +1,496 @@
|
|||
---
|
||||
title: "Sécurité logicielle : TD7 analyse statique / dynamique"
|
||||
date: 2023-03-10
|
||||
tags: ["Valgrind", "gcc"]
|
||||
author:
|
||||
- Yorick Barbanneau
|
||||
- Gwendal Aupee
|
||||
categories: ["Sécurité logicielle", "TD"]
|
||||
---
|
||||
|
||||
## Partie 1
|
||||
|
||||
### getpw
|
||||
|
||||
Le message d'avertissement de `gcc` est:
|
||||
|
||||
```
|
||||
`getpw' function is dangerous and should not be used.
|
||||
```
|
||||
|
||||
D'après le manuel de `getpw`, il n'y a pas de paramètre de taille, cette
|
||||
fonction est donc sensible aux attaques par dépassement de tampon (*buffer
|
||||
overflow*).
|
||||
|
||||
Nous avons diminué la taille de `buf` à 12 et observons une erreur de
|
||||
segmentation lors de la fin du programme. Avec analyse de la pile par `pframe`
|
||||
nous pouvons observer que le retour de `getpw()` écrase des élément au dessus de
|
||||
lui-même sur l'adresse de retour.
|
||||
|
||||
Il pourrait être possible d'intégrer un *shellcode* dans le champ *gecos* de
|
||||
notre utilisateur afin d'exploiter ce **dépassement de tampon**.
|
||||
|
||||
## mktemp
|
||||
|
||||
Cette fonction ne se charge pas de créer le fichier, elle défini juste un nom.
|
||||
Il est alors possible pour un attaquant de créer le fichier avant celui du
|
||||
programme en ratissant le plus large possible. Nous avions vu ce cas de figure
|
||||
lors du premier TD sur les défis *Nebula*.
|
||||
|
||||
## Partie 2
|
||||
|
||||
### Question 1
|
||||
|
||||
Effectivement `cppcheck` retourne une erreur dans le code source entrainant une
|
||||
fuite de mémoire :
|
||||
|
||||
```
|
||||
cppcheck test-leak.c
|
||||
Checking test-leak.c ...
|
||||
test-leak.c:9:2: error: Memory leak: d [memleak]
|
||||
return c;
|
||||
^
|
||||
```
|
||||
|
||||
Dasn le code source, les espaces mémoire doivent être désalloués :
|
||||
|
||||
* dans la fonction `f()` pour `d`
|
||||
* dans la fonction `g()` pour `c` (référencé sous `e` dans cette dernière)
|
||||
|
||||
En terme de sécurité, une fuite mémoire permet à une attaque par **déni de
|
||||
service**.
|
||||
|
||||
### Question 2
|
||||
|
||||
Ni `cppcheck` ni la version `asan` de l'exécutable ne remontent d'erreur. Seul
|
||||
`valgrind` nous averti d'un espace mémoire accessible à la terminaison du
|
||||
programme.
|
||||
|
||||
Valgrind nous donne des information sur la fuite de mémoire:
|
||||
|
||||
```
|
||||
valgrind --leak-check=full --show-leak-kinds=all ./test-reachable-leak
|
||||
==14383== Memcheck, a memory error detector
|
||||
==14383== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
|
||||
==14383== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
|
||||
==14383== Command: ./test-reachable-leak
|
||||
==14383==
|
||||
==14383==
|
||||
==14383== HEAP SUMMARY:
|
||||
==14383== in use at exit: 10 bytes in 1 blocks
|
||||
==14383== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
|
||||
==14383==
|
||||
==14383== 10 bytes in 1 blocks are still reachable in loss record 1 of 1
|
||||
==14383== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
|
||||
==14383== by 0x109146: main (test-reachable-leak.c:6)
|
||||
==14383==
|
||||
==14383== LEAK SUMMARY:
|
||||
==14383== definitely lost: 0 bytes in 0 blocks
|
||||
==14383== indirectly lost: 0 bytes in 0 blocks
|
||||
==14383== possibly lost: 0 bytes in 0 blocks
|
||||
==14383== still reachable: 10 bytes in 1 blocks
|
||||
==14383== suppressed: 0 bytes in 0 blocks
|
||||
==14383==
|
||||
==14383== For lists of detected and suppressed errors, rerun with: -s
|
||||
==14383== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
|
||||
```
|
||||
|
||||
## Partie 3
|
||||
|
||||
Effectivement, `cppcheck` nous averti à propos d'une zone mémoire créée mais non
|
||||
initialisées. La version `asan` ne fait aucun rapport mais *Valgrind* est lui un
|
||||
peu plus locace :
|
||||
|
||||
```
|
||||
[...]
|
||||
Conditional jump or move depends on uninitialised value(s)
|
||||
at 0x10917A: f (test-uninitialized.c:5)
|
||||
by 0x1091D0: main (test-uninitialized.c:13)
|
||||
[...]
|
||||
Use --track-origins=yes to see where uninitialised values come from
|
||||
[...]
|
||||
```
|
||||
|
||||
Lors du check de `test-uninitialized-printf.c` *Valgrind* nous averti sur
|
||||
l'usage de valeur non-initialisée dont dépendent des *jump* ou *move*
|
||||
conditionnels. Cette erreur provient de la variable `c` créée par un `malloc`
|
||||
mais non initialisée.
|
||||
|
||||
Avec l'option `--track-origins=yes` suggérée par sa première exécution,
|
||||
*Valgrind* nous informe de l'origine de l'allocation:
|
||||
|
||||
```
|
||||
[...]
|
||||
==14921== Conditional jump or move depends on uninitialised value(s)
|
||||
==14921== at 0x48BBEC7: __vfprintf_internal (vfprintf-process-arg.c:58)
|
||||
==14921== by 0x48B14FA: printf (printf.c:33)
|
||||
==14921== by 0x10917F: f (test-uninitialized-printf.c:5)
|
||||
==14921== by 0x1091A9: main (test-uninitialized-printf.c:10)
|
||||
==14921== Uninitialised value was created by a heap allocation
|
||||
==14921== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
|
||||
==14921== by 0x109194: main (test-uninitialized-printf.c:9)
|
||||
[...]
|
||||
```
|
||||
|
||||
## Partie 4
|
||||
|
||||
Lors du lancement du programme `test-undefined`, nous pouvons constater que la
|
||||
valeur afficher de notre variable `i` n'est pas celle attendue:
|
||||
|
||||
```bash
|
||||
./test-undefined
|
||||
-2147483648
|
||||
```
|
||||
|
||||
Comme nous l'avons vu en cours, nous sommes en présence d'un cas non définis par
|
||||
la norme C.
|
||||
|
||||
*Valgrind* lui de fait part d'aucun problème, il ne se soucis pas du type de la
|
||||
variable `i` mais de même espace qu'elle occupe en mémoire. Et il **n'a pas
|
||||
changé**.
|
||||
|
||||
``` shell
|
||||
valgrind ./test-undefined
|
||||
==3381== Memcheck, a memory error detector
|
||||
==3381== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
|
||||
==3381== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
|
||||
==3381== Command: ./test-undefined
|
||||
==3381==
|
||||
-2147483648
|
||||
==3381==
|
||||
==3381== HEAP SUMMARY:
|
||||
==3381== in use at exit: 0 bytes in 0 blocks
|
||||
==3381== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
|
||||
==3381==
|
||||
==3381== All heap blocks were freed -- no leaks are possible
|
||||
==3381==
|
||||
==3381== For lists of detected and suppressed errors, rerun with: -s
|
||||
==3381== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
|
||||
```
|
||||
|
||||
La version *usan* indique effectivement un overflow de notre variable `i` :
|
||||
|
||||
```
|
||||
./test-undefined.usan
|
||||
test-undefined.c:7:3: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
|
||||
-2147483648
|
||||
```
|
||||
|
||||
L'option de compilation `-fsanitize=undefined` utilisée pour cette version de
|
||||
l'exécutable ajoute des éléments pour vérifier les **comportements indéfinis**.
|
||||
|
||||
## Partie 5
|
||||
|
||||
### Question 1
|
||||
|
||||
`cppcheck` nous indique bien un *overrun* :
|
||||
|
||||
```bash
|
||||
cppcheck test-overrun.c
|
||||
Checking test-overrun.c ...
|
||||
Array 'c[10]' accessed at index 10, which is out of bonds.[arrayIndexOutOfBounds]
|
||||
c[10] = 1;
|
||||
^
|
||||
```
|
||||
|
||||
Le fichier en `.asan` nous indique un : *heap-buffer-overflow* avec plus de
|
||||
détails (registres pc bp et sp, adresses, etc) :
|
||||
|
||||
```
|
||||
/test-overrun.asan
|
||||
=================================================================
|
||||
==6036==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000001a
|
||||
at pc 0x55f0c2aa51cb bp 0x7ffc2aa141d0 sp 0x7ffc2aa141c8
|
||||
WRITE of size 1 at 0x60200000001a thread T0
|
||||
[...]
|
||||
Shadow bytes around the buggy address:
|
||||
0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
=>0x0c047fff8000: fa fa 00[02]fa fa fa fa fa fa fa fa fa fa fa fa
|
||||
0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
|
||||
0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
|
||||
[...]
|
||||
```
|
||||
|
||||
L'élément du tableau `c[10]` est incorrect car `malloc(10)` correspond à un
|
||||
tableau de 10 éléments avec des indices de **0 à 9** donc `c[10]` est
|
||||
*out-of-bond*.
|
||||
|
||||
LA version `usan` ne reporte aucune erreur, tout comme *Valgrind*.
|
||||
|
||||
### Question 2
|
||||
|
||||
Pour cette version, *Valgrind* ne reporte aucune erreur. Il ne se rend pas
|
||||
compte que l'accès en dehors de la variable dans le segment `data`. La version
|
||||
`usan` de notre exécutable elle affiche des erreurs:
|
||||
|
||||
```
|
||||
./test-overrun-data.usan
|
||||
test-overrun-data.c:7:3: runtime error: index 10 out of bounds for type 'char [10]'
|
||||
test-overrun-data.c:7:8: runtime error: store to address 0x55e89a7ca102
|
||||
with insufficient space for an object of type 'char'
|
||||
0x55e89a7ca102: note: pointer points here
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
^
|
||||
test-overrun-data.c:8:3: runtime error: index -1 out of bounds for type 'char [10]'
|
||||
```
|
||||
|
||||
`cppcheck` indique lui aussi deux erreurs pour `c[10]` et `c[-1]` :
|
||||
|
||||
```
|
||||
cppcheck test-overrun-data.c
|
||||
Checking test-overrun-data.c ...
|
||||
test-overrun-data.c:7:3: error: Array 'c[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]
|
||||
c[10] = 1;
|
||||
^
|
||||
test-overrun-data.c:8:3: error: Array 'c[10]' accessed at index -1, which is out of bounds. [negativeIndex]
|
||||
c[-1] = 1;
|
||||
^
|
||||
```
|
||||
|
||||
La version `asan` ne reporte non plus aucune erreur avec la version de *GCC*
|
||||
installée au *CREMI*. Cependant sur nos machines -- avec *Debian Unstable* comme
|
||||
système -- la version `asan` reporte une erreur.
|
||||
|
||||
Dans la version installée au CREMI, l'*address sanitizer* inclus dans *GCC*,
|
||||
l'option `-fno-common` n'est pas activée par défaut. Sans cette option il n'est
|
||||
pas possible pour lui de détecter le problème : il ne sais définir les limites
|
||||
des données globales non initialisés (stockées dans le segment *BSS*)
|
||||
|
||||
*Valgrind* ne se plaint pas et pour cause: nous se sommes toujours pas en
|
||||
présence d'un problème de fuite de mémoire.
|
||||
|
||||
### Question 3
|
||||
|
||||
Dans la version `asan`, l'option de compilation `-fsanitize=address` active par
|
||||
défaut l'option `asan-stack` chargée de vérifier les erreurs de type
|
||||
`out-of-bounds` et `use-after-free`.
|
||||
|
||||
Lorsque nous exécutons `test-overrun-stack` avec *GDB*, après l'instruction
|
||||
`c[-1] = 1` nous obtenons la pile suivante:
|
||||
|
||||
```
|
||||
[...]
|
||||
0x7fffffffe430 0x00007fffffffe520
|
||||
0x7fffffffe428 ret@ 0x00007ffff7dfb18a
|
||||
0x7fffffffe420 bp sp 0x0000000000000001 <- overflow de c[10]
|
||||
0x7fffffffe418 0x00007ffff7ffdad0
|
||||
0x7fffffffe410 0x0000010000000000 <- overlow de c[-1]
|
||||
[...]
|
||||
```
|
||||
|
||||
Avec l'aide le la commande `print`, nous pouvons alors vous les adresses de `c`
|
||||
|
||||
```
|
||||
(gdb) p &c[0]
|
||||
$4 = 0x7fffffffe416 ""
|
||||
(gdb) p &c[9]
|
||||
$5 = 0x7fffffffe41f ""
|
||||
```
|
||||
|
||||
Ici `c[10]` passe dans le segment de pile au dessus, déclenchant l'erreur dans
|
||||
notre exécutable `asan`.
|
||||
|
||||
## Partie 8
|
||||
|
||||
### Question 1
|
||||
|
||||
Le bug provient d'un problème de priorité : l'égalité est prioritaire sur tout
|
||||
le reste dans la condition:
|
||||
|
||||
```c
|
||||
// extract bit 0 to check for parity of the number of arguments
|
||||
if (argc & 1 == 0) {
|
||||
// ...
|
||||
```
|
||||
|
||||
Elle sera toujours fausse. La correction est simple:
|
||||
|
||||
```c
|
||||
// extract bit 0 to check for parity of the number of arguments
|
||||
if ((argc & 1) == 0) {
|
||||
// ...
|
||||
```
|
||||
|
||||
### Question 2
|
||||
|
||||
C'est à la ligne 718 qu'il faut chercher l'erreur:
|
||||
|
||||
```c
|
||||
while ((ret = xfs_bulkstat(fsfd,
|
||||
&lastino, GRABSZ, &buf[0], &buflenout) == 0)) {
|
||||
```
|
||||
|
||||
La parenthèse fermante définissant la priorité de l'affectation est mal placée,
|
||||
la comparaison se transforme donc en affectation. Utiliser cette notation permet
|
||||
d'affecter `ret` pendant la condition de la boucle, mais en contrepartie
|
||||
**augmente le risque d'erreur**.
|
||||
|
||||
### Question 3
|
||||
|
||||
L'instruction `assert` est mal utilisée, le simple signe égal indique une
|
||||
affectation et non une comparaison
|
||||
|
||||
```cpp
|
||||
assert(n = int(*(p+1)), "just checkin'...");
|
||||
```
|
||||
|
||||
La vérification sera toujours vrai, quoi qu'il arrive. Aucune erreur sera
|
||||
remontée et le programme continuera son exécution alors **qu'il ne devrait
|
||||
pas**.
|
||||
|
||||
`assert()` n'est utilisé que pour du *debug* dans le cas où la macro `NDEBUG`
|
||||
n'est pas définie au moment de la compilation. La portée de cette erreur était
|
||||
donc limitée.
|
||||
|
||||
### Question 4
|
||||
|
||||
L'outil d'analyse statique a détecté l'erreur car la méthode `screen->empty()`
|
||||
retourne un booléen, or ici aucune valeur de retour est utilisée.
|
||||
|
||||
### Question 5
|
||||
|
||||
```c
|
||||
for (i = 0; g_objArray[i] && i < MAX_WCOMP; i++) {
|
||||
MultiMoveRelXY(g_objArray[i], x - center, deltay);
|
||||
```
|
||||
|
||||
L'erreur d'accès est causée par `g_objArray[i]`, en effet le programme teste la
|
||||
validité de la case mémoire avant la validité de `i`. Le programme accède à la
|
||||
case mémoire avant de vérifier les limites.
|
||||
|
||||
Il est simple de fixer le problème: inverser les conditions comme ci-dessous:
|
||||
|
||||
```c
|
||||
for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
|
||||
MultiMoveRelXY(g_objArray[i], x - center, deltay);
|
||||
```
|
||||
|
||||
## Partie 9
|
||||
|
||||
### Question 1
|
||||
|
||||
La portion de code est appelée *dead code* car quel que soit les conditions,
|
||||
cette portion ne sera jamais atteinte. Ici, `decode` est définit à `false` et
|
||||
jamais modifiée (l. 160), ainsi de la condition l. 174 sera toujours fausse et
|
||||
donc les instructions dans la `if` jamais atteintes.
|
||||
|
||||
|
||||
### Question 2
|
||||
|
||||
La valeur de `skip` est modifiée à la ligne 751, puis est suivie d'une
|
||||
instruction `continue` qui relance un tour de boucle. La valeur de `skip` est
|
||||
alors réinitialisée à *1* au début de cette boucle. La modification effectuée
|
||||
alors est **totalement inutile**.
|
||||
|
||||
### Question 3
|
||||
|
||||
Le bloc d'instruction du `case` ligne 1240 n'est terminé ni par un `break` --
|
||||
permettant de sortir du `switch` -- ni d'un `return`. Ainsi l'exécution du
|
||||
programme continuer dans le `case` suivant (ou le `default`). Des portion de
|
||||
code peuvent alors être exécutées dans des cas non prévus.
|
||||
|
||||
### Question 4
|
||||
|
||||
Ici le test de la boucle `uint x >= 0` est toujours vrai, on entre alors dans une
|
||||
boucle infinie.
|
||||
|
||||
### Question 5
|
||||
|
||||
## Partie 10
|
||||
|
||||
### Question 1
|
||||
|
||||
### Question 2
|
||||
|
||||
`pos` pointe vers une variable locale a locale fonction `Texture::render()`.
|
||||
Lors de la fin de cette dernière, alors `pos` pointera vers une zone mémoire qui
|
||||
n'est plus disponible.
|
||||
|
||||
### Question 3
|
||||
|
||||
Dans la fonction `setParent( ExecutionNode parent)`, si `parent` est *NULL*
|
||||
alors une erreur de type *Null pointer dereferences* se produit lors de
|
||||
l'instruction `getContext().setParent(parent.getContext());`. Le développeur
|
||||
teste cependant la validité du pointeur juste avant.
|
||||
|
||||
### Question 4
|
||||
|
||||
L'analyseur a détermine que `pos` ne pouvait pas être égal à 0 pour deux
|
||||
raisons:
|
||||
|
||||
* la signature de la fonction `strchr` renvoie un pointeur, `p` contient donc
|
||||
une adresse
|
||||
* dans le cas ou aucun caractère correspondant n'est trouvé, le programme ne
|
||||
rentre pas dans la boucle c'est la condition
|
||||
`((pos = strchr(lastpos, '&')) != NULL)` qui le défini.
|
||||
|
||||
Si `pos` est défini, alors sont adresse est incrémentée de 1 (l. 1716), `pos`
|
||||
n'est donc jamais égal à 0, cependant il est possible que la case mémoire
|
||||
**pointée par `p` le soit**, indiquant une fin de chaine de caractères.
|
||||
|
||||
### Question 5
|
||||
|
||||
Là encore on est face à un problème entre le type pointeur (qui contient une
|
||||
adresse, sont un `int`) et la zone mémoire vers la quelle il pointe.
|
||||
|
||||
L'instruction suivante alloue une zone mémoire égale à un `int`:
|
||||
|
||||
```c
|
||||
Device_Entry *entry = calloc(1, sizeof(entry));
|
||||
```
|
||||
|
||||
Alors que l'instruction suivante alloue nue zone mémoire de la taille de
|
||||
`Device_Entry`:
|
||||
|
||||
```c
|
||||
Device_Entry *entry = calloc(1, sizeof(*entry));
|
||||
```
|
||||
|
||||
### Question 6
|
||||
|
||||
Lors de la première exécution de la boucle ` for (cur_module = wmodules;
|
||||
cur_module; wmodules = next_module)`, le programme libère la zone mémoire
|
||||
utilisée par la variable `cur_module` (l. 60). Cependant cette variable est
|
||||
utilisée ensuite dans le test de la boucle `for`. Tous les modules ne seront
|
||||
donc pas libérés (s'il y en a plus d'un).
|
||||
|
||||
```c
|
||||
for (cur_module = wmodules; cur_module; wmodules = next_module) {
|
||||
next_module = cur_module->next;
|
||||
cur_module->context->destroy(cur_module->data);
|
||||
free(cur_module);
|
||||
}
|
||||
```
|
||||
|
||||
Dans la correction proposée, `cur_module` est aussi utilisée dans la définition
|
||||
de la boucle `for`, ainsi là aussi notre boucle ne fera pas plus d'un tour.
|
||||
|
||||
## Partie 11
|
||||
|
||||
### Question 1
|
||||
|
||||
La dernière fonction appelée par ne donne pas lieu à un test comme les
|
||||
précédente, ainsi si tous les autres test réussissent mais pas ce dernier, alors
|
||||
le résultat sera vrai.
|
||||
|
||||
Le développeur aura du garder le fonctionnement des élément précédent et ainsi
|
||||
écrire:
|
||||
|
||||
```c
|
||||
if (!checkPriv(fromDBbackend, QLatin1String("CheckPriv_Cleanup")) {
|
||||
result = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Question 2
|
||||
|
||||
`TAINTED_STRING` signifie qu'un ou plusieurs éléments externes influnce(nt) le
|
||||
comportement de notre programme alors que ces éléments ne sont pas examinés.
|
||||
|
||||
Ici le code permet de lancer des commandes SQL contenues dans le fichier pointe
|
||||
par `optFile` sans aucun contrôle. Un attaquant pourrait alors réaliser une
|
||||
attaque par **injection SQL**.
|
Loading…
Add table
Add a link
Reference in a new issue