<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > AI生成的代碼你敢用嗎?有人給最近走紅的Copilot做了個(gè)「風(fēng)險評估」

AI生成的代碼你敢用嗎?有人給最近走紅的Copilot做了個(gè)「風(fēng)險評估」

發(fā)布人:機器之心 時(shí)間:2021-07-21 來(lái)源:工程師 發(fā)布文章

近日,GitHub 推出了一款利用人工智能生成模型來(lái)合成代碼的工具——Copilot,但發(fā)布之后卻飽受爭議,包括版權爭議、奇葩注釋 和涉嫌抄襲。除此之外,生成的代碼能不能用、敢不敢用也是一大問(wèn)題。在這篇文章中,Copilot 測試受邀用戶(hù) 0xabad1dea 在試用該代碼合成工具后發(fā)現了一些值得關(guān)注的安全問(wèn)題,并以此為基礎寫(xiě)了一份簡(jiǎn)單的風(fēng)險評估報告。

1.png

GitHub 真好,就算我因為 ICE 已經(jīng)叨擾了他們好幾百次,他們還是給予了我進(jìn)入 Copilot 測試階段的權限。這次,我不關(guān)心 Copilot 的效率,只想測試它的安全性。我想知道,讓 AI 幫人寫(xiě)代碼風(fēng)險有多高。

每一行提交的代碼都需要人來(lái)負責,AI 不應被用于「洗刷責任」。Copilot 是一種工具,工具要可靠才能用。木工不必擔心自己的錘子突然變壞,進(jìn)而在建筑物內造成結構性缺陷。同樣地,程序開(kāi)發(fā)者也應對工具保有信心,而不必擔心「搬起石頭砸自己的腳」。

在 Twitter 上,我的一位關(guān)注者開(kāi)玩笑說(shuō):「我已經(jīng)迫不及待想用 Copilot 寫(xiě)代碼了,我想讓它寫(xiě)一個(gè)用于驗證 JSON 網(wǎng)頁(yè) token 的函數,然后看都不看就提交上去?!?/p>

2.png

我按照這一思路使用了 Copilot,得到的結果很是搞笑:

function validateUserJWT(jwt: string): boolean {
    return true;
}

除了刪除硬盤(pán)驅動(dòng)器之外,這可能是最糟糕的實(shí)現了。這種錯誤是如此明顯、粗陋,任何專(zhuān)業(yè)的程序開(kāi)發(fā)者對此都不會(huì )有異議。我更感興趣的是 Copilot 是否會(huì )生成乍一看很合理的代碼,以至于其中的錯誤會(huì )被程序員忽視或被經(jīng)驗不足的程序員認為是正確的。(劇透一下:確實(shí)會(huì )出現這種情況。)

我有意使用 Copilot 生成實(shí)際應該人工編寫(xiě)的代碼,因為用戶(hù)肯定會(huì )在生產(chǎn)過(guò)程中這樣做。

不確定性

Copilot 是一種生成模型,這意味著(zhù)其目的是生成在統計學(xué)意義上近似其輸入(即訓練數據)的輸出結果。其目標并不是精準重現其輸入,因為單純的復現并不需要機器學(xué)習系統。這類(lèi)模型有一個(gè)通常被稱(chēng)為「溫度(temperature)」的參數,這是一個(gè)滑動(dòng)比例,可在保守風(fēng)格(模仿最常見(jiàn)的輸入)和原創(chuàng )風(fēng)格之間調整。原創(chuàng )性設置越高,輸出的結構化程度就越低,就越有可能得到垃圾結果。Copilot 的溫度由 GitHub 控制,據說(shuō)他們會(huì )根據當前試驗階段而對其進(jìn)行調節。

以同樣的方式調用一個(gè)生成模型兩次,通常并不會(huì )得到兩個(gè)同樣的結果。它每次都會(huì )重新運行一次自己的統計模型。這有其優(yōu)勢——如果你不喜歡該模型的前一個(gè)建議,你可以再次提問(wèn)。這也能減少 Copilot 為不同用戶(hù)生成相同代碼的數量。但是,這與可靠性最基本的原則之一(即確定性)并不一致。它有可能在某一天完全按照你的樣本文件執行,而在第二天又完全做錯。

舉個(gè)例子,使用完全一樣的輸入(一個(gè)僅包含注釋、關(guān)鍵詞 “function” 和一個(gè)空格的文件),我得到了以下兩個(gè)不同的月相計算器實(shí)現:

月相計算器 A

// returns the current phase of the moon
function getPhase() {
    var phase = Math.floor((new Date().getTime() - new Date().setHours(0,0,0,0)) / 86400000) % 28;
    if (phase == 0) {
        return "New Moon";
    } else if (phase == 1) {
        return "Waxing Crescent";
    } else if (phase == 2) {
        return "First Quarter";
    } else if (phase == 3) {
        return "Waxing Gibbous";
    } else if (phase == 4) {
        return "Full Moon";
    } else if (phase == 5) {
        return "Waning Gibbous";
    } else if (phase == 6) {
        return "Last Quarter";
    } else if (phase == 7) {
        return "Waning Crescent";
    }
}

月相計算器 B

// returns the current phase of the moon
function getPhase() {
    var phase = Math.floor((new Date().getTime() - new Date(new Date().getFullYear(), 0, 0).getTime()) / (1000 * 60 * 60 * 24)) + 1;
    if (phase > 29) {
        phase = 29;
    }
    return phase;
}

粗略一看,它們完成的任務(wù)好像一樣,但其中一個(gè)實(shí)現會(huì )將一個(gè)月份分為 8 個(gè)月相,每個(gè)月相包含若干天,然后返回日期對應的月相文本標簽;另一個(gè)則像是將每個(gè)月份的每一天視為一個(gè)不同的月相且沒(méi)有文本標簽。但實(shí)際上它們都做錯了。計算器 A 說(shuō) 2021 年 7 月 2 日是新月,而人工編寫(xiě)的天文學(xué)網(wǎng)站則表示 2021 年 7 月 2 日是殘月。計算器 B 則在 phase 值高得離譜(>29)時(shí),將月相設為29。這兩個(gè)實(shí)現乍一看都是可行的,而如果你比較一些結果,你會(huì )發(fā)現很難說(shuō)其中哪一個(gè)是正確的。

實(shí)際上,我在生成一個(gè)復活節日期計算器時(shí)多次得到了完全一樣的輸出結果,而且這個(gè)計算器是正確的(至少在我驗證過(guò)的一些年份上是正確的)。我猜想這意味著(zhù)復活節計算器實(shí)現之間的偏差要比月相計算器之間的偏差小得多。

復活節計算器

// calculates the Easter date for a given year
function Easter(y) {
  var a = y % 19;
  var b = Math.floor(y / 100);
  var c = y % 100;
  var d = Math.floor(b / 4);
  var e = b % 4;
  var f = Math.floor((b + 8) / 25);
  var g = Math.floor((b - f + 1) / 3);
  var h = (19 * a + b - d - g + 15) % 30;
  var i = Math.floor(c / 4);
  var k = c % 4;
  var l = (32 + 2 * e + 2 * i - h - k) % 7;
  var m = Math.floor((a + 11 * h + 22 * l) / 451);
  var n0 = (h + l + 7 * m + 114)
  var n = Math.floor(n0 / 31) - 1;
  var p = n0 % 31 + 1;
  return new Date(y, n, p);
}

可解讀性

上面的復活節計算器是正確的,但我也只是通過(guò)實(shí)驗知道的;它實(shí)在太難以解讀了。(更新:有人在評論區指出有一個(gè)書(shū)寫(xiě)錯誤會(huì )影響少量年份——這是逃過(guò)了我的檢驗的漏洞?。?/p>

Copilot 可以并且有時(shí)候肯定會(huì )增加注釋?zhuān)谶@里沒(méi)有影響。其中的變量名也完全毫無(wú)用處。我毫不懷疑其中一些是沒(méi)有明確名稱(chēng)的中間結果,但整體而言,它能夠做到更加清晰。有時(shí)候,回到開(kāi)始從注釋的起點(diǎn)開(kāi)始調用,會(huì )讓 Copilot 試圖給出解釋。舉個(gè)例子,在函數中間提示 //f is 會(huì )讓 Copilot 聲明 // f is the day of the week (0=Sunday),但這似乎并不對,因為復活節星期日(Easter Sunday)往往是在星期日。其還會(huì )聲明 // Code from http://www.codeproject.com/Articles/1114/Easter-Calculator ,但這似乎并非一個(gè)真實(shí)網(wǎng)站鏈接。Copilot 生成的注釋有時(shí)候是正確的,但并不可靠。

我嘗試過(guò)一些與時(shí)間相關(guān)的函數,但僅有這個(gè)復活節計算器是正確的。Copilot 似乎很容易混淆不同類(lèi)型的計算日期的數學(xué)公式。舉個(gè)例子,其生成的一個(gè)「格列高利歷到儒略歷」轉換器就是混雜在一起的計算星期幾的數學(xué)公式。即使是經(jīng)驗豐富的程序員,也很難從統計學(xué)上相似的代碼中正確辨別出轉換時(shí)間的數學(xué)公式。

密鑰以及其它機密信息

真實(shí)的密碼學(xué)密鑰、API 密鑰、密碼等機密信息永遠都不應該發(fā)布在公開(kāi)的代碼庫中。GitHub 會(huì )主動(dòng)掃描這些密鑰,如果檢測到它們,就會(huì )向代碼庫持有者發(fā)出警告。我懷疑被這個(gè)掃描器檢測出的東西都被排除在 Copilot 模型之外,雖然這難以驗證,但當然是有益的。

這類(lèi)數據的熵很高(希望如此),因此 Copilot 這樣的模型很難見(jiàn)過(guò)一次就完全記住它們。如果你嘗試通過(guò)提示生成它,那么 Copilot 通常要么會(huì )給出一個(gè)顯而易見(jiàn)的占位符「1234」,要么就會(huì )給出一串十六進(jìn)制字符——這串字符乍看是隨機的,但基本上就是交替出現的 0-9 和 A-F。(不要刻意使用它來(lái)生成隨機數。它們的語(yǔ)法是結構化的,而且 Copilot 也可能向其他人建議同樣的數字。)但是,仍然有可能用 Copilot 恢復真實(shí)的密鑰,尤其是如果你使用十個(gè)而非一個(gè)建議打開(kāi)一個(gè)窗格時(shí)。舉個(gè)例子,它向我提供了密鑰 36f18357be4dbd77f050515c73fcf9f2,這個(gè)密鑰在 GitHub 上出現了大約 130 次,因為它曾被用于布置家庭作業(yè)。任何在 GitHub 上出現過(guò) 100 次以上的東西都不可能是真正敏感的東西。最現實(shí)的風(fēng)險是天真的程序員接收自動(dòng)填充的密碼作為加密密鑰,這會(huì )讓所得到的值看起來(lái)隨機,但其熵卻很低很危險。

通過(guò)提示來(lái)生成密碼會(huì )得到各種有趣的不安全樣本。在訓練數據中,這些樣本通常是作為占位字符串使用的。大家最喜歡的占位字符串是「mongoose」。對一些用戶(hù)而言,生成臟話(huà)詞匯可能會(huì )造成一些問(wèn)題。

證書(shū)清洗

GitHub 已經(jīng)公開(kāi)表示他們在 Copilot 模型中包含了托管于該網(wǎng)站的所有公開(kāi)代碼,并且不管證書(shū)如何。很明顯,他們認為這算是正當使用,不受制于證書(shū)限制,但這樣的意見(jiàn)在法庭上是否站得住腳…… 還有待觀(guān)察。

可以很容易驗證,Copilot 包含 GPL 代碼,因為 Copilot 可以很容易從記憶中引用 GPL 證書(shū)文本。用 Copilot 寫(xiě)出類(lèi)似于某些具有獨特命名慣例的 GPL 項目的代碼也很容易。

關(guān)鍵在于,Copilot 可用于「證書(shū)清洗」,做法是通過(guò)提示讓其對不想要證書(shū)下的代碼進(jìn)行細微的修改。對于使用 Copilot 的所有人而言,這有可能突然成為一個(gè)大的法律問(wèn)題,也可能不會(huì )成為問(wèn)題。

安全漏洞示例:用 C 寫(xiě)的 HTML 解析器

一位朋友建議使用「具有正則表達式的通用 HTML 解析器」來(lái)為 Copilot 提供提示,這恰好是一個(gè)你不應該做的例子;Copilot 實(shí)際上拒絕使用正則表達式,而是編寫(xiě)了一個(gè)完備的 C 函數和相當好的 main() 來(lái)驅動(dòng)它。我做出的唯一修改是注釋掉 free(html),因為 free() 沒(méi)有通過(guò) include 定義并且在任何情況下都不是必需的。

#include <stdio.h>
// general purpose HTML parser with regex
void parse(char *html) {
    int i = 0;
    while (html[i] != '\0') {
        if (html[i] == '<') {
            // find the end of the tag
            int j = i + 1;
            while (html[j] != '>') {
                j++;
            }
            // find the end of the attribute
            int k = j + 1;
            while (html[k] != ' ' && html[k] != '\0') {
                k++;
            }
            // copy the tag and attribute to a new string
            char tag[j - i + 1];
            char attr[k - j + 1];
            for (int m = 0; m < j - i; m++) {
                tag[m] = html[i + m];
            }
            tag[j - i] = '\0';
            for (int m = 0; m < k - j; m++) {
                attr[m] = html[j + m];
            }
            attr[k - j] = '\0';
            printf("%s %s\n", tag, attr);
            // skip the end of the tag
            i = k;
        } else {
            i++;
        }
    }
}
int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("usage: %s <html file>\n", argv[0]);
        return 1;
    }
    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL) {
        printf("error opening %s\n", argv[1]);
        return 1;
    }
    char *html = NULL;
    size_t len = 0;
    if (getline(&html, &len, fp) == -1) {
        printf("error reading %s\n", argv[1]);
        return 1;
    }
    parse(html);
    //free(html); // copilot inserted this but it's not declared and not necessary anyway
    return 0;
}

這是格式良好甚至還有注釋的 C 代碼,它當然看起來(lái)像是能解析 HTML,其 main 函數在打開(kāi)文件時(shí)有一些很有用的樣板。但是,其解析過(guò)程有很多問(wèn)題。

首先,最重要的是,如果被解析的字符串不包含 >,那么解析器就會(huì )直接運行到緩沖區的末端并導致崩潰。

這個(gè)解析器完全不清楚單獨的 < 和引用的 ">" 之間的區別,并會(huì )直接考慮首先出現的 >,而不會(huì )考慮其語(yǔ)法功能。

屬性的定義是緊跟在右括號之后的非空白序列。舉個(gè)例子,在 <body>glory 中,glory 是一個(gè)屬性。屬性屬于它們的標簽內部,并且可以有多個(gè)以空格分隔的屬性。

沒(méi)有任何報錯能力。

好消息是其有數量驚人的設計精妙的指針數學(xué),其工作起來(lái)就像是…… 之前已經(jīng)設計好的。很難說(shuō)這是預先設計好的,因為這個(gè)函數其實(shí)并沒(méi)有做什么有用的事情,盡管它與基礎解析器的功能差不多有 80% 相似。當然,因為運行到緩沖區末端而直接引發(fā)的崩潰是一個(gè)致命的安全問(wèn)題。

安全漏洞示例:用 PHP 寫(xiě)的 SQL 注入

前兩行是我的提示。

<?php
 // connects to the database
 include('../db_connect.php');
 // grabs the variables from the URL
 $id = $_GET['id'];
 // makes the connection to the database
 $mysqli = new mysqli($hostname, $username, $password, $database);
 // checks for errors
 if(mysqli_connect_errno())
 {
  // we could not connect to the database, so output a message
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
 }
 // executes the query
 $query = "SELECT * FROM users WHERE id = '$id'";
 $result = $mysqli->query($query);
 ...

這個(gè)樣板直接犯了大錯,產(chǎn)生了 2000 年代早期最典型的安全漏洞:PHP 腳本采用原始的 GET 變量并將其插入到用作 SQL 查詢(xún)的字符串中,從而導致 SQL 注入。對于 PHP 初學(xué)者來(lái)說(shuō),犯這樣的錯無(wú)可厚非,因為 PHP 文檔和生態(tài)系統很容易導致他們犯這種錯誤?,F在,PHP 那臭名昭著(zhù)的容易誘導人出錯的問(wèn)題甚至也對非人類(lèi)生命產(chǎn)生了影響。

此外,當提示使用 shell_exec() 時(shí),Copilot 很樂(lè )于將原始 GET 變量傳遞給命令行。

有趣的是,當我添加一個(gè)僅是 htmlspecialchars() 的 wrapper 的函數時(shí)(Copilot 決定將其命名為 xss_clean()),它有時(shí)候會(huì )記得在渲染數據庫結果時(shí)讓這些結果通過(guò)這個(gè)過(guò)濾器。但只是有時(shí)候。

安全漏洞示例:Off By One

我為 Copilot 給出提示,讓其寫(xiě)一個(gè)基本的監聽(tīng) socket。其大有幫助地寫(xiě)了大量樣板,并且編譯也毫不費力。但是,這個(gè)函數在執行實(shí)際的監聽(tīng)任務(wù)時(shí)會(huì )出現基本的 off-by-one 緩沖溢出錯誤。

一個(gè)打開(kāi) socket 并將命令收入緩沖區的函數

// a function that opens a socket and accepts commands into a buffer
int accept_commands(int sockfd) {
   char buffer[1024];
   int n;
   while (1) {
       n = read(sockfd, buffer, sizeof(buffer));
       if (n < 0) {
           perror("read");
           exit(1);
       }
       if (n == 0) {
           printf("connection closed\n");
           exit(0);
       }
       buffer[n] = '\0';
       printf("%s\n", buffer);
   }
   return 0;
}

如果緩沖區填滿(mǎn),buffer[n] 可能指向超過(guò)緩沖區末端之后再一個(gè),這會(huì )導致超出邊界的 NUL 寫(xiě)入。這個(gè)例子很好地表明:這類(lèi)小漏洞在 C 中會(huì )如野草般生長(cháng),在實(shí)際情況下它是有可能被利用的。對于使用 Copilot 的程序員而言,因為未注意到 off-by-one 問(wèn)題而接受這種代碼還是有可能的。

總結

這三個(gè)有漏洞的代碼示例可不是騙人的,只要直接請求它寫(xiě)出執行功能的代碼,Copilot 就很樂(lè )意寫(xiě)出它們。不可避免的結論是:Copilot 可以而且將會(huì )常常寫(xiě)出有安全漏洞的代碼,尤其是使用對內存不安全的語(yǔ)言編寫(xiě)程序時(shí)。

Copilot 擅于編寫(xiě)樣板,但這些樣板可能阻礙程序開(kāi)發(fā)人員找到好的部分;Copilot 也能很準確地猜測正確的常數和設置函數等等。但是,如果依賴(lài) Copilot 來(lái)處理應用邏輯,可能很快就會(huì )誤入歧途。對此,部分原因是 Copilot 并不能總是維持足夠的上下文來(lái)正確編寫(xiě)連綿多行的代碼,另一部分原因是 GitHub 上有許多代碼本身就存在漏洞。在該模型中,專(zhuān)業(yè)人員編寫(xiě)的代碼與初學(xué)者的家庭作業(yè)之間似乎并沒(méi)有系統性的區分。神經(jīng)網(wǎng)絡(luò )看到什么就會(huì )做什么。

請以合理質(zhì)疑的態(tài)度對待 Copilot 生成的任何應用邏輯。作為一位代碼審查員,我希望人們能清楚地標記出哪些代碼是由 Copilot 生成的。我預期這種情況無(wú)法完全解決,這是生成模型工作方式的基本問(wèn)題。Copilot 可能還將繼續逐步改進(jìn),但只要它能夠生成代碼,它就會(huì )繼續生成有缺陷的代碼。

原文鏈接:https://gist.github.com/0xabad1dea/be18e11beb2e12433d93475d72016902

*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: AI

相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>