1. 程式人生 > >java安全沙箱(二)之.class檔案檢驗器

java安全沙箱(二)之.class檔案檢驗器

參考:

java是一種型別安全的語言,它有四類稱為安全沙箱機制的安全機制來保證語言的安全性,這四類安全沙箱分別是:

  - 類載入體系

  - .class檔案檢驗器

  - 內置於Java虛擬機器(及語言)的安全特性

  - 安全管理器及Java API

本篇部落格主要介紹.class檔案檢驗器的基本原理;如需瞭解其它幾類安全機制可以通過上面的部落格連結進入檢視。

簡介

  jvm的.class檔案檢驗器用於檢查.class檔案是否擁有合法的記憶體結構,這種檢查是有必要的,因為java的.class檔案可能來自本機,也可能來自網路,可能是你自己編譯的檔案,也可能是別人篡改過的檔案。而對於jvm來說,一個.class檔案就是一個位元組序列,它不會過問位元組序列的來源,只會校驗位元組序列的結構是否正確。

  .class檔案檢驗器保證安全的措施就是檢驗.class檔案位元組碼的健壯性,比如某個.class檔案是被惡意篡改過的,這個.class檔案中包含一個方法,該方法有一條goto指令,直接跳到方法外部去執行未知的程式碼,如果執行該方法,很可能會導致jvm崩潰。所以,由.class檔案檢驗器檢查位元組碼的健壯性是很有必要的。

  雖然.class檔案檢驗器檢查位元組碼能保證程式的健壯性,然後這是需要犧牲一些效能的;為了將這種影響降到最低,.class檔案檢驗器會在位元組碼執行之前完成大部分的檢驗工作,也就是說,.class檔案檢驗器會在位元組碼執行前而不是執行中進行檢查,而且這種檢查只會進行一次

。比如每次遇到一條跳轉指令.class檔案檢驗器都會確認該跳轉指令跳轉到了另外一條合法指令,而該指令是是在同一方法的位元組流中的。

  .class檔案檢驗器會進行四次獨立的掃描來保證位元組碼的合法性。第一趟掃描在類裝載時進行,這次會檢查.class檔案的內部結構,以保證它能被安全的編譯;第二趟和第三趟掃描是在連線時進行的,這時會檢查.class檔案的資料定義是否遵從了java語言的語義規範,還會檢查位元組碼的完整性;第四趟掃描是在解析符號引用時進行的,這次會檢查.class檔案所引用的欄位、方法和類是否存在。

第一趟掃描

  在.class檔案被裝載時,會進行第一次掃描,這時.class檔案檢驗器會檢查每一條位元組序列,看它是否符合java class檔案的基本結構。首先,檢驗器會檢查.class檔案是否以魔數0XCAFEBABY開頭,這個魔數的作用是為了區分一些明顯錯誤的或被破壞的.class檔案。然後,檢驗器會檢查.class檔案的主版本號和次版本號,這個版本號必須在jvm所支援的範圍之內,比如我用java8編譯編譯的.class檔案放在java6的jvm內執行會丟擲java.lang.UnsupportedClassVersionError,因為java6不支援java8的一些新特性比如lambda表示式,所以不能去執行java8的編譯器編譯的位元組碼;然後java一般都是向後相容的,比如java8的jvm是能執行java6編譯的.class檔案的。第一趟掃描主要是檢查.class檔案是否遵從了java class檔案的固定格式

,這樣才能位元組碼編譯成方法區內的內部資料結構。

第二趟掃描

  第二趟掃描會進行資料型別的語義檢查,它不會再去檢查.class檔案的二進位制資料了,而是會去檢查方法區中定義的資料結構。類中定義的欄位、方法和方法描述符等在.class檔案中都會儲存為一個字串,檢驗器會檢查這些字串是否符合java規範;檢驗器還會檢查類本身是否符合java語言規範,比如java語言規定除了Object類外所有類都必須有一個父類;檢查器還會檢查被final修飾的類(比如String.class)是否被繼承,同樣也會檢查被final修飾的方法是否被覆蓋,如果出現錯誤會丟擲java.lang.VerifyError。檢驗器還會檢查常量池裡定義的常量是否合法,它會檢查常量的索引會指向正確的常量池條目。

第三趟掃描

  第三趟掃描會進行位元組碼驗證,位元組碼檢查會確認位元組碼的操作碼的正確性,也會確保運算元棧包含正確的數值和型別,還會檢查類的方法被呼叫時會傳入正確的引數和引數型別。檢驗器在第三趟掃描會進行大量的操作,比如會檢驗所有的操作碼都有一個合法的運算元等,在這趟掃描過後,它需要保證.class檔案的位元組碼流可以被jvm安全地執行。然後檢驗器並不能檢查出所有的安全問題,比如“停機問題”它就不能檢查出來。停機問題是一個著名的計算機領域問題,即不能寫出一個程式,用它來判斷作為其輸入的某個程式在執行時是否會停機。

第四趟掃描

  第四趟掃描是在符號的動態連線時進行檢查,檢驗器會檢查符號引用是否合法;因為這次掃描需要檢查該class類所引用的類,所以這是可能需要裝載新的類;然而為了節約記憶體並保證程式正確性,jvm會使用延遲載入的策略來裝載類,即直到類被程式真正使用時才會去裝載。如果一個jvm實現為了加快裝載速度預先裝載了類,它還是會表現為延遲裝載。比如jvm在預裝載某個類時發現這個類不存在,它並不會馬上丟擲NoClassDefFoundError,而是直到這個缺少的類被程式使用時才丟擲異常。如果jvm進行預先連線,第四次掃描會緊接著第三次掃描立刻執行;而如果jvm進行延遲連線(即在某個引用第一次執行時才連線),那麼第四趟掃描可能在第三趟掃描之後很久才執行(甚至不執行)。