java安全沙箱之class文件檢驗器,讀完之後,大部分程序員收藏了.


java安全沙箱之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進行延遲連接(即在某個引用第一次執行時才連接),那麼第四趟掃描可能在第三趟掃描之後很久才執行(甚至不執行)。

關注

感謝閱讀,如果這篇文章幫助了您,歡迎 點贊收藏,關注轉發 喲。您的幫助是我們前行的動力,我們會提供更多有價值的內容給大家... 謝謝!


分享到:


相關文章: