# F05: review
contributed by < `zodf0055980` >
###### tags: `Linux 核心設計`
## 第 2 週測驗題 測驗 3
```clike=
#include <stdio.h>
#define cons(x, y) (struct llist[]){x, y}
struct llist { int val; struct llist *next; };
int main() {
struct llist *list = cons(9, cons(5, cons(4, cons(7, NULL))));
struct llist *p = list;
for (; p; p = p->next)
printf("%d", p->val);
printf("\n");
return 0; }
```
會選擇這題是因為第一次看到這種技巧,這種技巧為 Compound literals,在 C99 中的 6.5.2.5 有講述 Compound literals 的行為。
`The type name shall specify an object type or an array of unknown size, but not a variable length array type.`
可看到它可以對未知大小的矩陣做初始話的動作。因此可以利用 Compound literals 去省下指標的操作。此外這種方法也可以套用到函式上,下面做一個簡單的實做:
```clike=
#include <stdio.h>
struct S
{
int x, y;
};
void prints(struct S p)
{
printf("%d, %d", p.x, p.y);
}
int main()
{
prints((struct S){2, 3});
return 0;
}
```
執行這個程式可以看到結果是` 2, 3`
因此題目中的`struct llist *list = cons(9, cons(5, cons(4, cons(7, NULL))));`
其實可以解讀成 `list->val = 9; list->next = cons(5, cons(4, cons(7, NULL)))) `再依此往下帶入
最後會形成 head->9->5->4->7->NULL 的 linklist。
這種方法在 linux kernel 也時常看見,例如在 [linux/arch/sh/include/asm/atomic.h](https://github.com/torvalds/linux/blob/7796916146b8c34cbbef66470ab8b5b28cf47e83/arch/sh/include/asm/atomic.h)就有這段
```clike=
#define ATOMIC_INIT(i) { (i) }
```
便是使用 Compound literals 去進行附值。
## 第 3 週測驗題 (上) 測驗 1
```clike
#include <stdint.h>
#include <stddef.h>
int memcmp(const uint8_t *m1, const uint8_t *m2, size_t n) {
for (size_t i = 0; i < n; ++i ) {
int diff = m1[i] - m2[i];
if (diff != 0) return (diff < 0) ? -1 : +1;
}
return 0;
}
```
這種寫法是去一一筆比較每一個記憶體位置看是否相符,一遇到不相符就回傳 -1 或 1。但這種方法容易有資料外洩的問題,如果我們利用這種一一比較的方式去比較密碼的話,我們可以利用函式執行時間去判斷密碼前面幾個正確,因此便可以利用這種方式去利用大量資料破解密碼。我下面做個測試:
```clike
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
int memcmp(const uint8_t *m1, const uint8_t *m2, size_t n) {
for (size_t i = 0; i < n; ++i ) {
int diff = m1[i] - m2[i];
if (diff != 0) return (diff < 0) ? -1 : +1;
}
return 0;
}
static int diff_in_ns(struct timespec t1, struct timespec t2)
{
struct timespec diff;
if (t2.tv_nsec - t1.tv_nsec < 0) {
diff.tv_sec = t2.tv_sec - t1.tv_sec - 1;
diff.tv_nsec = t2.tv_nsec - t1.tv_nsec + 1000000000;
} else {
diff.tv_sec = t2.tv_sec - t1.tv_sec;
diff.tv_nsec = t2.tv_nsec - t1.tv_nsec;
}
return (diff.tv_sec * 1000000000 + diff.tv_nsec);
}
int main()
{
char *password = "ABCDEFGHIJKLMNOPQRSTUV";
char error[23][23] = {
"XXXXXXXXXXXXXXXXXXXXXX",
"AXXXXXXXXXXXXXXXXXXXXX",
"ABXXXXXXXXXXXXXXXXXXXX",
"ABCXXXXXXXXXXXXXXXXXXX",
"ABCDXXXXXXXXXXXXXXXXXX",
"ABCDEXXXXXXXXXXXXXXXXX",
"ABCDEFXXXXXXXXXXXXXXXX",
"ABCDEFGXXXXXXXXXXXXXXX",
"ABCDEFGHXXXXXXXXXXXXXX",
"ABCDEFGHIXXXXXXXXXXXXX",
"ABCDEFGHIJXXXXXXXXXXXX",
"ABCDEFGHIJKXXXXXXXXXXX",
"ABCDEFGHIJKLXXXXXXXXXX",
"ABCDEFGHIJKLMXXXXXXXXX",
"ABCDEFGHIJKLMNXXXXXXXX",
"ABCDEFGHIJKLMNOXXXXXXX",
"ABCDEFGHIJKLMNOPXXXXXX",
"ABCDEFGHIJKLMNOPQXXXXX",
"ABCDEFGHIJKLMNOPQRXXXX",
"ABCDEFGHIJKLMNOPQRSXXX",
"ABCDEFGHIJKLMNOPQRSTXX",
"ABCDEFGHIJKLMNOPQRSTUX",
"ABCDEFGHIJKLMNOPQRSTUV",
};
FILE *fp = fopen("time.txt", "wb+");
int i;
for (i=0; i<22; i++) {
char *com = &error[i][0];
struct timespec start, end;
clock_gettime(CLOCK_REALTIME, &start);
int get = memcmp(com,password, 23);
clock_gettime(CLOCK_REALTIME, &end);
fprintf(fp, "%d %d\n", i, diff_in_ns(start, end));
}
return 0;
}
```
![](https://i.imgur.com/tFNgUDl.png)
若改用 constant-time 的比較
![](https://i.imgur.com/lsObL0d.png)
:::info
像是全錯或是只有第一個 char 正確
為何前幾次執行的時間特別高呢?
:::
觀察 [linux/lib/string.c](https://github.com/torvalds/linux/blob/master/lib/string.c) 中我們常用到的 strcmp 以及 strncmp
```clike
int strcmp(const char *cs, const char *ct)
{
unsigned char c1, c2;
while (1) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
}
return 0;
}
int strncmp(const char *cs, const char *ct, size_t count)
{
unsigned char c1, c2;
while (count) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
count--;
}
return 0;
}
```
可以發現都不是使用 constant-time。