--- tags: linux2021, --- # 2021q1 Homework1 (lab0) contributed by < `lofoz` > ## 程式碼實作 ### queue.h #### queue_t ```cpp typedef struct { list_ele_t *head; /* Linked list of elements */ /* tail -> record the last ELE * size -> record the queue size */ list_ele_t *tail; int size; } queue_t; ``` 宣告 `int size` 紀錄 queue 的長度 為了達到 `insert tail` Time complexity $O(1)$ 的要求,宣告 `list_ele_t tail` ### queue.c #### q_new() ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* Got a null pointer */ if (!q) return NULL; q->head = NULL; /* initialize queue tail */ q->tail = NULL; /* initialize queue size */ q->size = 0; return q; } ``` 變數初始化 #### q_insert_head() ```cpp= bool q_insert_head(queue_t *q, char *s) { list_ele_t *newh; /* Got a null pointer */ if (q == NULL) return false; /* allocate spcae */ newh = malloc(sizeof(list_ele_t)); newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (newh == NULL || newh->value == NULL) return false; /* Copy string */ strcpy(newh->value, s); newh->next = q->head; q->head = newh; /* initialize queue tail when queue size is 0 */ if (q->size == 0) q->tail = q->head; /* update queue size */ q->size++; return true; } ``` 上方的版本在第 7 和第 8 行,會發生 Error: Memory leak ,第 12 行的 `strcpy`() 為 Dangerous function,下方為修改後 ```cpp /* allocate spcae */ newh = malloc(sizeof(list_ele_t)); /* Got a null pointer */ if (!newh) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (!newh->value) { free(newh); return false; } ``` ```cpp /* Copy string and check if the string size is too long */ int buf_len = strlcpy(newh->value, s, strlen(newh->value) + 1); if (buf_len >= strlen(newh->value) + 1 || buf_len < 0) { free(newh->value); free(newh); return false; } ``` :::warning 若 `char *s` 傳入時為 NULL,會發生什麼事?查閱 man page 以理解實作行為。 :notes: jserv ::: #### q_insert_tail() ```cpp bool q_insert_tail(queue_t *q, char *s) { list_ele_t *newt; /* Got a null pointer */ if (!q) return false; /* allocate space */ newt = malloc(sizeof(list_ele_t)); /* Got a null pointer */ if (!newt) return false; newt->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (!newt->value) { free(newt); return false; } /* Copy string and check if the string size is too long */ int buf_len = strlcpy(newt->value, s, strlen(newt->value) + 1); if (buf_len >= strlen(newt->value) + 1 || buf_len < 0) { free(newt->value); free(newt); return false; } newt->next = NULL; if (q->size == 0) { q->head = newt; q->tail = q->head; } else { q->tail->next = newt; q->tail = newt; } /* update queue size */ q->size++; return true; } ``` #### q_remove_head() ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { list_ele_t *tmp; /* Check if queue is NULL or empty */ if (!q || q->size == 0) return false; /* Copy string */ if (sp) { size_t len = strlen(q->head->value) + 1; len = len > bufsize ? bufsize : len; strncpy(sp, q->head->value, len); sp[len - 1] = '\0'; } tmp = q->head; q->head = q->head->next; /* update queue size */ q->size--; /* Check tail */ if (q->size == 0) q->tail = NULL; /* Free space */ free(tmp->value); free(tmp); return true; } ``` #### q_reverse() ```cpp void q_reverse(queue_t *q) { /* Check if q is NULL */ if (q) { list_ele_t *cur = q->head; list_ele_t *prev = NULL; list_ele_t *tmp = NULL; /* traversal queue */ while (cur) { tmp = cur; cur = cur->next; tmp->next = prev; prev = tmp; } /* Swap tail and head */ tmp = q->head; q->head = q->tail; q->tail = tmp; } } ``` #### q_free() ```cpp void q_free(queue_t *q) { if (!q) return; list_ele_t *cur = q->head; while (cur) { list_ele_t *tmp = cur; cur = cur->next; free(tmp->value); free(tmp); } free(q); } ``` #### q_sort() ```cpp void q_sort(queue_t *q) { /* forget !q->head */ if (!q || !q->head) return; merge_sort(&q->head); /* more efficiency way to find tail? */ while (q->tail->next) { q->tail = q->tail->next; } } ``` ## 測試結果 ### make test 得分 ![](https://i.imgur.com/3uvHfqL.png) :::danger 文字訊息不要用圖片展現,避免排版和日後檢索的困難,同時也對視覺障礙者更友善。 :notes: jserv ::: ## 遇到的困難 ### 1. VIM 縮排長度 ```diff queue_t *q = malloc(sizeof(queue_t)); /* Got a null pointer */ if (!q) - return NULL; + return NULL; ``` VIM 預設縮排寬度和 Git hook 需求不同 #### 解法 1. 使用 clang-format 自動修正排版 ```shell $ clang-format -i *.[ch] ``` 2. 或許可以透過調整 VIM 設定檔(待確認) :::warning 善用 [EditorConfig](https://editorconfig.org/) :notes: jserv ::: ### 2. Dangerous function : strcpy() 遇到的錯誤訊息 ```cpp Dangerous function detected in /home/lofoz/linux2020/lab0-c/queue.c 54: strcpy(newh->value, s); 85: strcpy(newt->value, s); ``` #### 解法 查閱 [Common vulnerabilities guide for C programmers](https://security.web.cern.ch/recommendations/en/codetools/c.shtml) 後,了解為何 strcpy 是有風險的函式: > The strcpy built-in function does ==not check buffer lengths== and may very well ==overwrite memory zone contiguous to the intended destination==. In fact, the whole family of functions is similarly vulnerable: **strcpy**, **strcat** and **strcmp**. 從說明中得知除了 **strcpy**,還有其他同家族的函式(**strcat**, **strcmp**)等,皆是有風險的函式 使用 snprintf() 有效的執行字串複製,並確保記憶體複寫(overwrite)的問題 修改如下 ```cpp= #ifndef strlcpy #define strlcpy(dst, src, sz) snprintf((dst), (sz), "%s", (src)) #endif ``` 並且在使用到 strlcpy() 的部分修改為 ```cpp= /* Copy string and check if the string size is too long */ int buf_len = strlcpy(newh->value, s, strlen(newh->value) + 1); if (buf_len >= strlen(newh->value) + 1 || buf_len < 0) { return false; } ``` strlcpy() 會回傳字串 s 的長度 buf_len (不包含終止符號 '\0'),若 buf_len 長度 $\geq$ strlen(newh->value) + 1,則 return false ### 3. Warning: nullPointerRedundantCheck ```cpp= queue.c:53:5: warning: Either the condition '!newh' is redundant or there is possible null pointer dereference: newh. [nullPointerRedundantCheck] newh->value = malloc(sizeof(char) * (strlen(s) + 1)); ^ queue.c:55:9: note: Assuming that condition '!newh' is not redundant if (!newh || !newh->value) ^ queue.c:53:5: note: Null pointer dereference newh->value = malloc(sizeof(char) * (strlen(s) + 1)); ^ queue.c:87:5: warning: Either the condition '!newt' is redundant or there is possible null pointer dereference: newt. [nullPointerRedundantCheck] newt->value = malloc(sizeof(char) * (strlen(s) + 1)); ^ queue.c:89:9: note: Assuming that condition '!newt' is not redundant if (!newt || !newt->value) ^ queue.c:87:5: note: Null pointer dereference newt->value = malloc(sizeof(char) * (strlen(s) + 1)); ^ ``` 透過 qtest 測試認為程式是沒有問題後,執行 commit ,結果跳出一長串的 warning (有被嚇到 :P) 透過仔細查看程式碼後,發現問題是未檢查 nullPointerRedundantCheck 就直接使用 pointer 舉 q_insert_head() 的片段,狀況如下 ```cpp= /* allocate spcae */ newh = malloc(sizeof(list_ele_t)); newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (!newh || !newh->value) return false; ``` #### 解法 將程式碼修改成如下 ```cpp= /* allocate spcae */ newh = malloc(sizeof(list_ele_t)); /* Got a null pointer */ if (!newh) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (!newh->value) return false; ``` 雖然寫起來較為繁雜,但卻能確保記憶體空間的正確獲取。 ### 4. Error: Memory leak e.g. 在 q_insert_head() 中發生 Memory leak ```cpp= /* allocate spcae */ newh = malloc(sizeof(list_ele_t)); /* Got a null pointer */ if (!newh) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /* Got a null pointer */ if (!newh->value) return false; ``` 問題發生於上方的第 8 行,若 `newh->value` 沒有拿到記憶體空間,則回傳 false,乍看之下沒有問題,但函式返回時並未釋放 newh 的記憶體空間。 #### 解法 修改如下 在 return false 前,釋放 newh 的記憶體空間 ```cpp /* Got a null pointer */ if (!newh->value) { free(newh); return false; } ``` ### 5. Segmentation fault occurred: Dereferenced a NULL or invalid pointer 就在我寫完所有 function 開心的下了 make test 之後,<s>噴了一堆錯誤</s>,其中最先要解決的是 Dereferenced a NULL or invalid pointer。 :::danger 避免說「噴了一堆錯誤」,這符合漢語的慣例嗎?工程人員說話要精準。 :notes: jserv ::: 如下方的錯誤訊息 ```cpp= +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, and sort ERROR: Removed value bear != expected value meerkat Segmentation fault occurred. You dereferenced a NULL or invalid pointer --- trace-05-ops 0/5 +++ TESTING trace trace-06-string: # Test of truncated strings Segmentation fault occurred. You dereferenced a NULL or invalid pointer --- trace-06-string 0/6 +++ TESTING trace trace-07-robust: # Test operations on NULL queue Segmentation fault occurred. You dereferenced a NULL or invalid pointer --- trace-07-robust 0/6 ``` #### 解法 透過開啟對應的 trace file ,得知某些 function 漏掉檢查 queue 是否為 NULL 或 q->head 是否為 NULL ### 6. ``` ==5696==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55c58770e620 at pc 0x55c5876f6ae8 bp 0x7ffc96c1c5e0 sp 0x7ffc96c1c5d0 READ of size 4 at 0x55c58770e620 thread T0 #0 0x55c5876f6ae7 in do_option_cmd /home/lofoz/linux2020/lab0-c/console.c:369 #1 0x55c5876f58d0 in interpret_cmda /home/lofoz/linux2020/lab0-c/console.c:221 #2 0x55c5876f60b5 in interpret_cmd /home/lofoz/linux2020/lab0-c/console.c:244 #3 0x55c5876f72e1 in cmd_select /home/lofoz/linux2020/lab0-c/console.c:594 #4 0x55c5876f785b in run_console /home/lofoz/linux2020/lab0-c/console.c:667 #5 0x55c5876f43e5 in main /home/lofoz/linux2020/lab0-c/qtest.c:780 #6 0x7ff6754580b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) #7 0x55c5876f1b8d in _start (/home/lofoz/linux2020/lab0-c/qtest+0x8b8d) 0x55c58770e621 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:21:6' (0x55c58770e620) of size 1 'simulation' is ascii string '' SUMMARY: AddressSanitizer: global-buffer-overflow /home/lofoz/linux2020/lab0-c/console.c:369 in do_option_cmd Shadow bytes around the buggy address: 0x0ab930ed9c70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab930ed9c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab930ed9c90: 00 00 00 00 00 00 00 00 f9 f9 f9 f9 00 f9 f9 f9 0x0ab930ed9ca0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 0x0ab930ed9cb0: f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 =>0x0ab930ed9cc0: f9 f9 f9 f9[01]f9 f9 f9 f9 f9 f9 f9 00 00 00 00 0x0ab930ed9cd0: 01 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9 0x0ab930ed9ce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ab930ed9cf0: 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 0x0ab930ed9d00: 01 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9 0x0ab930ed9d10: 04 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==5696==ABORTING ``` :::info **Todo:** 了解 EditorConfig, char *s 傳入時為 NULL 之行為 :::