English
 找回密码
 立即注册

Ethereum Chain Audit Report: go-ethereum Security Audit

Vitalik 2025-11-6 06:48 44138人围观 ETH

Recently, Ethereum go-ethereum made public two audit reports, and the Xuanmao security team translated them immediately. This is the first "Go Ethereum Security Review", namely 2017-04-25_Geth-audit_Truesec. This audit report was completed on April 25, 2
Recently, Ethereum go-ethereumTwo audit reports were made public, and the Xuanmao security team translated them immediately. This is the first "Go Ethereum Security Review" that is 2017-04-25_Geth-audit_Truesec, this audit report was completed on April 25, 2017. If you are using an older version  go-ethereum, it is strongly recommended to upgrade to the latest version to avoid unnecessary losses.

1.1. Overview


TrueSecIn April 2017, a code audit was conducted on Ethereum’s GO language implementation. The audit results show that the code quality is relatively high and developers have a certain level of security awareness. No serious security vulnerabilities were discovered during the audit. One of the most serious vulnerabilities is the bypass of the web browser's same-origin policy when RPC HTTP is turned on on the client side. Other identified issues do not have direct attack vectors to exploit, and the remainder of the report contains general comments and recommendations.

1.2. Table of Contents

1.2.1. P2P and the Internet


Known issues

Memory allocation is too large

1.2.2. Transaction and block processing


divide by zero risk

code complexity

1.2.3. IPC and RPC interface


CORS: Allow all domains by default in HTTP RPC

1.2.4. Javascript engine and API


Weak random seeds for pseudo-random number generators

1.2.5. EVM implementation


abuseintPoolResulting in cheap memory consumption

Fragile negative value protection in mining blocks

1.2.6. Miscellaneous


Conditional competition in mining code

Many third party dependencies

1.3. Results details

1.3.1. P2P and the Internet


TrueSecrightp2pThe code of the network part was audited, focusing mainly on:

Secure channel implementation - handshake and sharingsecretsrealization

Secure Channel Properties - Confidentiality and Integrity

Serialization of messages

Node discovery

Protection against DOS: timeouts and message size limits

TrueSecRLP decoding was also fuzzed through go-fuzz, and no node crash was found.

1.3.1.1. Known issues

Although共享secretsexistencryption handshakeIt is implemented better in symmetric encryption algorithm, but due to thetwo-time-padDefects make the channel lack confidentiality. This is a known issue. (For details, refer to [https://github.com/ethereum/devp2p/issues/32](https://github.com/ethereum/devp2p/issues/32)and[https://github.com/ethereum/go-ethereum/issues/1315](https://github.com/ethereum/go-ethereum/issues/1315)). Since the current channel only transmits public blockchain data, this problem does not need to be solved for the time being.

Another ongoing issue is the lack of a secure channel level (a default time-based replay protection mechanism was mentioned in the Ethereum developer discussion)重放保护TrueSecIt is recommended that the next version of the protocol be implemented by controlling the number of messages重放保护

1.3.1.2. Memory allocation is too large

existrlpx.goTrueSecTwo user-controllable, excessive memory allocations were found.TrueSecNo exploitable DOS scenarios have been found, but it is recommended to properly verify them.

When reading protocol messages,16.8MBMemory of size can be allocated:
func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {    ...    fsize := readInt24(headbuf)    // ignore protocol type for now    // read the frame content    var rsize = fsize // frame size rounded up to 16 byte boundary    if padding := fsize % 16; padding > 0 {    rsize += 16 - padding    }    // TRUESEC: user-controlled allocation of 16.8MB:    framebuf := make([]byte, rsize)    ...}
Since in the Ethereum protocol, the maximum message size is defined as10MB,TrueSecIt is recommended that memory allocations are also defined to be the same size.

existencryption handshakeDuring the process, handshake information can be assigned65KBsize memory.
func readHandshakeMsg(msg plainDecoder, plainSize int,                      prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {    ...    // Could be EIP-8 format, try that.    prefix := buf[:2]    size := binary.BigEndian.Uint16(prefix)    if size < uint16(plainSize) {        return buf, fmt.Errorf("size underflow, need at least ...")    }    // TRUESEC: user-controlled allocation of 65KB:    buf = append(buf, make([]byte, size-uint16(plainSize)+2)...)    ...}
Unless the handshake message does contain65KBsize data,TrueSecIt is recommended to limit the size of handshake messages.

1.3.2. Transaction and block processing


TrueSecConducted code audits on transactions, block downloads, and block processing, focusing mainly on:

allocated from memory,gorountine泄露andIO操作resulting in denial of service

Sync issues

1.3.2.1. Divide by zero risk

In Go, dividing by zero results in apanic. exist downloader.goofqosReduceConfidenceIn the method, whether division by zero occurs depends on the caller calling it correctly:
func (d *Downloader) qosReduceConfidence() {    peers := uint64(d.peers.Len())    ...    // TRUESEC: no zero-check of peers here    conf := atomic.LoadUint64(&d.rttConfidence) * (peers - 1) / peers    ...}
TrueSecNo exploit has been found that can cause node crashes, but it relies solely on the caller to ensured.peers.Len()It is unsafe to be non-zero.TrueSecIt is recommended that all non-constant dividends should be checked before division is performed.

1.3.2.2. Code complexity

TrueSecIt was found that the code parts for transaction and block processing are more complex than other parts of the code and are more difficult to read and audit. This part of the method is relatively larger, in fetcher.go,downloader.go andblockchain.goThere are over 200 lines of code in . Implementations of synchronization sometimes combine mutex locks and channel messages. For example, the structure DownloaderThe definition requires 60 lines of code, including 3 mutexes and 11 channels.

Code that is difficult to read and understand is fertile ground for security problems. in particular ethThere are some code-intensive methods, structures, interfaces and extended mutexes and channels in the package.TrueSecIt is recommended to spend some effort to refactor and simplify the code to prevent future security issues.

1.3.3. IPC and RPC interface


TrueSecThe IPC and RPC (HTTP and Websocket) interfaces were audited, focusing on potential access control issues, from public API privilege escalation to private API (admin, debug, etc.).

1.3.3.1. CORS: Allow all domains in default HTTP RPC

HTTP RPCThe interface can be passedgethof--rpcParameters are enabled. This starts a web server that listens for HTTP requests on port 8545 and is accessible to anyone. Due to the possibility of potentially exposing ports (such as connecting to untrusted networks), only the public API allows HTTP RPC interfaces by default.

The Same Origin Policy and the default Cross-Origin Resource Sharing (CORS) configuration restrict web browser access and limit the possibility of attacking the RPC API via XSS.allowed originsable to pass--rpccorsdomain "domain"To configure, you can also configure multiple domain names separated by commas.-- rpccorsdomain "domain1,domain2", or configure为--rpccorsdomain "*", making all domains accessible through standard web browsers. If not configured, the CORS header will not be set - and the browser will not allow cross-origin requests:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8545/. (Reason: CORS header 'Access-Control-Allow-Origin missing').
Due to missing CORS header,FirefoxCross-domain requests are prohibited.

However, in commit 5e29f4b (from April 12, 2017) - the same-origin policy can be bypassed and RPC can be accessed from a web browser.

The CORS configuration for HTTP RPC was changed to handleallowed originsarray of characters - rather than being transferred internally as a single-quote-delimited string.

Previously, the comma-separated string was split into an array, upon instantiationcorsBefore middleware (see Listing 1). with default value (in case the user does not explicitly configure any settings, such as using --rpccorsdomain) empty string, which will cause a character array to contain an empty string.

existcommit 5e29f4bAfter that, the default value is an empty array, which is passed to thenewCorsHandlermiddlewarecors(See Listing 2).

corsThe middleware then checksallowed originsThe length of the array (see Listing 3). If the length is 0, which in this case represents an empty array, the cors middleware will become the default and allow all fields.

This problem can be solved by runninggeth -rpcTo reproduce, no need to specify anyallowed origins, and checkcommit 5e29f4bWith front and rearOPTIONCORS headers of the request. The second output Access-Control-Allow-OriginWorth noting.

Note that this was true here even before the change. If it were not due to string splitting, the corsThere is no explanation for the input value (an array containing an empty string) being empty.

This problem can be solved through the followingJavaScriptCode to exploit, executed from any domain (even the local file system, i.e. invalid or null origin)
var xhr = new XMLHttpRequest();xhr.open("POST", "http://localhost:8545", true);xhr.setRequestHeader("Content-Type", "application/json");xhr.onreadystatechange = function() {    if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {    console.log("Modules: " + xhr.responseText);    }}xhr.send('{"jsonrpc":"2.0","method":"rpc_modules","params":[],"id":67}')
TrueSecIt is recommended that the default configuration of CORS be explicitly restricted (such asallowed originset tolocalhost, or not setting the CORS header at all), rather than relying on the outside world to choose a normal (safe) default
165 func newCorsHandler(srv *Server, corsString string) http.Handler {166     var allowedOrigins []string167     for _, domain := range strings.Split(corsString, ",") {168         allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))169     }170     c := cors.New(cors.Options{171         AllowedOrigins: allowedOrigins,172         AllowedMethods: []string{"POST", "GET"},173         MaxAge: 600,174         AllowedHeaders: []string{"*"},175     })176     return c.Handler(srv)177 }
Listing 1: rpc/http.go, before commit 5e29f4be935ff227bbf07a0c6e80e8809f5e0202
164 func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler {165     c := cors.New(cors.Options{166         AllowedOrigins: allowedOrigins,167         AllowedMethods: []string{"POST", "GET"},168         MaxAge: 600,169         AllowedHeaders: []string{"*"},170     })171     return c.Handler(srv)172 }
Listing 2: rpc/http.go, after commit 5e29f4be935ff227bbf07a0c6e80e8809f5e0202
113     // Allowed Origins114     if len(options.AllowedOrigins) == 0 {115     // Default is all origins116     c.allowedOriginsAll = true117     }
Listing 3: vendor/github.com/rs/cors/cors.go
$ curl -i -X OPTIONS    -H "Access-Control-Request-Method: POST"    -H "Access-Control-Request-Headers: content-type"    -H "Origin: foobar" http://localhost:8545HTTP/1.1 200 OKVary: OriginVary: Access-Control-Request-MethodVary: Access-Control-Request-HeadersDate: Tue, 25 Apr 2017 08:49:10 GMTContent-Length: 0Content-Type: text/plain; charset=utf-8
Listing 4: CORS headers before commit 5e29f4b
$ curl -i -X OPTIONS    -H "Access-Control-Request-Method: POST"    -H "Access-Control-Request-Headers: content-type"    -H "Origin: foobar" http://localhost:8545HTTP/1.1 200 OKAccess-Control-Allow-Headers: Content-TypeAccess-Control-Allow-Methods: POSTAccess-Control-Allow-Origin: foobarAccess-Control-Max-Age: 600Vary: OriginVary: Access-Control-Request-MethodVary: Access-Control-Request-HeadersDate: Tue, 25 Apr 2017 08:47:24 GMTContent-Length: 0Content-Type: text/plain; charset=utf-8
Listing 5: CORS headers after commit 5e29f4b

1.3.4. JavaScript engine and API


The JavaScript engine otto is the CLI script interface in Go Ethereum, a terminal interactive interpreter for IPC/RPC interface, and is also privatedebug APIpart of. Considering its limited code, it has a lower priority in auditing.

1.3.4.1. Weak random number seeds for pseudo-random number generation

existjsreWhen initializing the pseudo-random number generator, ifcrypto/rand(crypto/randIf the method fails, the random number seed will depend on the current UNIX time. In listing 6, this weak random number seed will be used for initialization math/randinstance.

thisPRNGis not used for any sensitive information and obviously should not be used for cryptographic securityRNG, but since users can run the script through the command line to usePRNG, it is obviously safer to make it fail rather than create a weak random number seed. from crypto/randGetting an error means there may be a problem somewhere else. Even if a secure random number seed is obtained, it should be noted in the documentation PRNGIt is not cryptographically secure.
84 // randomSource returns a pseudo random value generator.85 func randomSource() *rand.Rand {86     bytes := make([]byte, 8)87     seed := time.Now().UnixNano() // 不是完全随机88     if _, err := crand.Read(bytes); err == nil {89         seed = int64(binary.LittleEndian.Uint64(bytes))90     }9192     src := rand.NewSource(seed)93     return rand.New(src)94 }
Listing 6: internal/jsre/jsre.go

1.3.5. Implementation of Ethereum Virtual Machine (EVM)


TrueSecThe code of the Ethereum Virtual Machine (EVM) part was audited, focusing on denial of service caused by misuse of memory allocation and IO operations. There is an EVM interpreter (runtime/fuzz.go) go-fuzzentry point, this entry point is used successfully.TrueSecIts functionality was confirmed, but no impactful vulnerabilities were discovered during the fuzzing process.

1.3.5.1. Cheap memory consumption caused by abusing intPool

Due to performance reasons, during the execution of the EVM, large integers will enter the integer pool.intPool(intpool.go). Since there is no limit on the size of the integer pool, use a specific opcodecombination, will result in unexpected cheap use of memory.
0 JUMPDEST      // 1 gas1 COINBASE      // 2 gas2 ORIGIN        // 2 gas3 EQ            // 3 gas, puts 20 + 20 bytes on the intpool4 JUMP          // 8 gas, puts 4-8 bytes on the intpool
For example, the contract code will consume 3.33e9 units of gas (approximately worth 3300USD at the time) and allocate 10G memory tointPool. Expectation of 10GB memory allocation in Ethereum Virtual Machine gasThe cost is 1.95e14 (approximately 195,000,000USD)

whenintPoolproduceout of memory panicThis can lead to a denial of service attack. But the consensus algorithm is gaslimitRestrictions have been implemented to prevent this denial of service attack from occurring. But consider that an attacker might find a more effective padding intPoolway, orgaslimit targetGrowth is too rapid, etc.TrueSecStill recommended forintPoolsize limit.

1.3.5.2. Fragile negative value protection in mining blocks

Ethereum transfers between accounts are viacore/evm.goinsideTransfermethod.
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {    db.SubBalance(sender, amount)    db.AddBalance(recipient, amount)}
enteramountIs a pointer to a signed type, possibly with negative reference values. a negative amountEthereum will be transferred from the payee to the transfer party, allowing the transfer party to steal Ethereum from the payee.

When an unpacked transaction is received, the value of the transaction is verified to be positive. like tx_pool.go, validateTx():
if tx.Value().Sign() < 0 {    return ErrNegativeValue}
But there is no such explicit verification during block processing.; Transactions with negative values ​​are only implicitly p2pSerialization format (RLP) blocks, and RLP cannot decode negative values. Suppose an evil miner publishes a block with negative transactions in order to illegally obtain Ethereum. At this time, relying on a specific serialization format to provide protection seems a bit fragile. TrueSecIt is recommended to also explicitly check the value of transactions during block processing. Or use an unsigned type to force the value of the specified transaction to be positive.

1.3.6. Miscellaneous


1.3.6.1. Conditional competition in mining code

TrueSecUse "-race" to build flags and passGoThe race condition detection feature is built into the language to look for race conditions. exist ethash/ethash.goFound a similar one used in miningethash datasetsTimestamp related conditions race.
func (ethash *Ethash) dataset(block uint64) []uint32 {    epoch := block / epochLength    // If we have a PoW for that epoch, use that    ethash.lock.Lock()    ...    current.used = time.Now() // TRUESEC: race    ethash.lock.Unlock()    // Wait for generation finish, bump the timestamp and finalize the cache    current.generate(ethash.dagdir, ethash.dagsondisk, ethash.tester)    current.lock.Lock()    current.used = time.Now()    current.lock.Unlock()...}
To remove race conditions, usecurrent.lockA mutex protects the firstcurrent.usedsettings.   TrueSecThere is no research on whether conditional competition will affect the mining of nodes.

1.3.6.2. Too many third-party dependencies

Go EthereumDepends on 71 third-party packages (viagovendor list +vendenumeration)

Since each dependency potentially introduces new attack vectors and requires time and effort to monitor for security vulnerabilities,TrueSecIt is always recommended to keep the number of third-party packages to a minimum.

71 dependencies is relatively large for any project.TrueSecIt is recommended that Ethereum developers investigate whether all dependencies are truly needed, or whether some of them can be replaced with code.

1.4. Appendix

1.4.1. Statement


We strive to provide accurate translations. Some parts may not be accurate, and some content is not important and has not been translated. Please refer to the original text if necessary.

1.4.2. Original address


https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25Geth-auditTruesec.pdf

1.4.3. Reference links

Reference projectURL address
go ethereumhttps://ethereum.github.io/go-ethereum/
go fuzzhttps://github.com/dvyukov/go-fuzz/
commit 5e29f4https://github.com/ethereum/go-ethereum/commit/5e29f4be935ff227bbf07a0c6e80e8809f5e0202
cors middlewarehttps://github.com/rs/cors
ottohttps://github.com/robertkrimen/otto

*Reference source: github, compiled and compiled by Javierlev@Xuanmao Security Team. Please indicate the source of reprint from FreeBuf.COM.



精彩评论0
我有话说......
TA还没有介绍自己。