Просто если в функции не контролируется переполнение буффера, ты зная его длинну можешь туда напихать, потом переписать канарейку, адрес возврата на следующую строку и следущей строкой пойдет твой код на исполнение и ты можешь что хочешь там сделать в правах того пользователя от которого программа запущена, ну а потом программу просто вышибет конечно, но вред то уже нанесен
Канарейка не контролирует переполнение буфера, она контролирует порчу стека.
Допустим у тебя программа содержит уязвимость и канарейку, ты переписал RET в стеке но канарейка испорчена, проверка не пройдена и гудбай, программа вылетает.
Соответственно планы получить шел накрылись медным тазом.
Но тут есть нюанс, например уязвимость в функции которая исполняется в форкнутом потоке, что тогда произойдет? А вот что, чайлд закроется с сегфолтом, как положено, но родительский поток то останется и канарейка в следующем чайлде будет такая же как и в предыдущем.
И это дает возможность по байтику подобрать и канарейку и все остальное, что перед канарейкой лежит (а это уже упрощает поиск базового адреса ЛИБС и соответственно атаку на неисполняемый стек и ту самую смену адресов ЛИБС)
Конечно програм использующих форк много меньше програм выполняющихся в одном потоке, но почти все много поточные программы работают в сфере интернет серверов Веб или ФТП и могут быть атакованы удаленно, что опаснее по многим причинам.