惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 - Better Mathjax Rendering [Extending Hexo For My Site] Part 0 - Preface Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 不能说的秘密
Invalid Golang Pointers Can Bite You Even If You Don't Dereference
2022-04-18 · via hsfzxjy 的博客

In Golang, if you coerce a uintptr variable into unsafe.Pointer (or further, to some *T), the linter will warn with the message "possible miuse of unsafe.Pointer". This makes sense because the uintptr variable may contain an address that points to a piece of invalid memory, and dereferencing such a pointer is catastrophic (usually aborts the program).

I was always aware of the above discipline, but I thought it would be OK to hold the pointers but not dereference them. This is true in C/C++, but not for Golang, which I did not realize until recently.

In fact, the program can panic even if you just keep an invalid pointer on the stack!

A strange invalid pointer panic

The story back from an attempt of interoperation between Golang and JVM, when I was working on a Go-written dynamic library which need to operate bluetooth socket on Android. Android does not provide any native interfaces for bluetooth, so I had to call into JVM and invoke Java APIs.

I have learned JNI 1 beforehand, which is an interface designed for interacting with JVM from native codes. Since JNI is provided to programmers as C++ header files, I had to seek a Golang binding. Then I noticed xlab/android-go which, as utilities, encapsulates the full list of JNI types and functions. The project was out of maintenance for some while, but using only the JNI pieces should be fine.

With the help of xlab/android-go, I quickly finished a prototype of my library, so good, so far. I bundled the library into apk file, ran it on my phone, but unfortunately it crashed with the stack strace

runtime: bad pointer in frame kcore_android/bluetooth.ioWorker.Loop at 0x400018eeb0: 0x1
fatal error: invalid pointer found on stack

runtime stack:
runtime.throw({0x7dbf000df2?, 0x7dbf19b4a0?})
/usr/local/go/src/runtime/panic.go:992 +0x50 fp=0x7d980b6c70 sp=0x7d980b6c40 pc=0x7dbf058ff0
runtime.adjustpointers(0x7d980b7000?, 0x36581?, 0x7dbf164983?, {0x7dbf192338?, 0x7dbf19b4a0?})
/usr/local/go/src/runtime/stack.go:628 +0x1cc fp=0x7d980b6cb0 sp=0x7d980b6c70 pc=0x7dbf0716cc
runtime.adjustframe(0x7d980b7000, 0x7d980b70f8)
/usr/local/go/src/runtime/stack.go:670 +0xa4 fp=0x7d980b6d40 sp=0x7d980b6cb0 pc=0x7dbf0717b4
runtime.gentraceback(0x7d00001000?, 0x7d980b7140?, 0xffffff80ffffffe0?, 0x40001824e0, 0x0, 0x0, 0x7fffffff, 0x7dbf116168, 0x43?, 0x0)
/usr/local/go/src/runtime/traceback.go:330 +0x734 fp=0x7d980b7060 sp=0x7d980b6d40 pc=0x7dbf07b7d4
runtime.copystack(0x40001824e0, 0x1000)
/usr/local/go/src/runtime/stack.go:930 +0x300 fp=0x7d980b7220 sp=0x7d980b7060 pc=0x7dbf071fa0
runtime.newstack()
/usr/local/go/src/runtime/stack.go:1110 +0x37c fp=0x7d980b73d0 sp=0x7d980b7220 pc=0x7dbf0723fc
runtime.morestack()
/usr/local/go/src/runtime/asm_arm64.s:314 +0x70 fp=0x7d980b73d0 sp=0x7d980b73d0 pc=0x7dbf084bc0

goroutine 51 [copystack, locked to thread]:
--- snip ---

I was not frightened, since no code would succeed in one go. But the error report did frustrate me from two perspectives

  1. It involved one of my stack frames (kcore_android/bluetooth.ioWorker.Loop), but the panic was thrown from some source code that lies out of my codebase (runtime/stack.go).
  2. It was caused by an invalid pointer, whose value was 0x1.

I guessed the pointer was returned from the Java side, for some unknown reason it had a wierd value of 0x1. But what I didn’t understand is how it could crash my program. I have tried carefully to avoid dereferencing any non-Go pointer in my code.

Also, the mismatch between stack frame and source code made me really difficult to locate the problem. For a time I thought goroutine 51 stopped at the scene where the pointer troubled, as its stack trace contained the aforementioned frame bluetooth.ioWorker.Loop, but it didn’t. In fact, the goroutine stopped at another line when I restarted the program! This was annoying.

It took me almost half a day to resolve and understand the problem. I will first explain the origin of the invalid pointer, and then show how it would crash the program.

The origin of 0x1 pointer

In JNI, the C type jobject acts as a handle for Java object, which is technically an alias of void*. They can be created by calling most JNI functions like JNIEnv->CallObjectMethod.

Although being a pointer type, a jobject variable is not necessarily a valid pointer. To understand one should know that there exists two kinds of object references in JNI, local reference and global reference. Local references will be recycled at the end of a Java frame, while global references survive longer until you delete them.

They not only differ semantically, but practically diverse in values. Local references often contain smaller values like 0x01, 0x75, yet global references will have values like 0x7dbeffc1cf. I guess local references are not actual pointers but indices of some internal object tables.

Symmetrically, xlab/android-go defines a Jobject which was an alias for unsafe.Pointer. So if you recieve a local reference from JNI functions, you are owning an invalid pointer at Go side.

Go runtime checks invalid pointers during stack growth

What’s interesting is that, goroutines do not statically allocate their stack. Instead, they are able to grow or shrink the stack according to our needs. I will not dive into the details of this mechanism, which you may read from the article Go: How does the goroutine stack size evolve? if you are interested.

My panic was thrown by an invalid pointer checking during stack growing. Why should the Go runtime check for invalid pointers here? Because growing a stack involves memory re-allocation, and the runtime must ensure no pointer is invalidated after the potential moving.

To see how a moving could invalidate pointers, let’s consider an example. Say we have a goroutine whose stack ranged in address space 0x8000 - 0x8800. An integer i int was stored at 0x8000, and a pointer ptr *int referenced to that int stored at 0x8004, whose value is 0x8000. Now we grow the stack by moving it to address space 0xA000 - 0xB000. If ptr retains its old value, it will no longer point to i since i has been moved to 0xA000! Therefore, during a stack growth, Go runtime must also check the existence for such pointers, and change their values accordingly.

However, the Go runtime does more than checking whether or not a pointer value falls in the old address space range. It also checks and complains about pointers with small values

func adjustpointers(/*...*/) {

if f.valid() && 0 < p && p < minLegalPointer && debug.invalidptr != 0 {


getg().m.traceback = 2
print("runtime: bad pointer in frame ", funcname(f), " at ", pp, ": ", hex(p), "\n")
throw("invalid pointer found on stack")
}

}

The above snippet can be found at runtime/stack.go. If a pointer value is less than minLegalPointer (which is 4096), the runtime will also panic! And that’s the culprit for my case.

Conclusion

Now I know that the panic comes from two aspects. First I have an invalid pointer due to FFI, although I don’t mean to dereference it. The Go runtime, however, does more than I thought behind the scene. It moves the goroutine stack when necessary, during which it checks and complains for invalid pointers.

This reminds me not to coerce foreign pointer-like values into Go pointers, if you won’t dereference them at the Go side. The safest practice is to keep them uintptr. As a solution, I patch and slim xlab/android-go into hsfzxjy/android-jni-go, which works like a charm.

I also create a minimal example to reproduce the above problem, for whom interested to investigate. In this example, the main goroutine stack will grow during the invocation of foo() -> bar() -> baz(), during which the Go runtime encounters the crafted pointer ptr, and eventually panics.

package main

import (
"fmt"
"unsafe"
)

func main() {
var a [10]int
foo(a)
}


func foo(a [10]int) {
var b [100]int
ptr := unsafe.Pointer(uintptr(1))
bar(b)
fmt.Printf("%p\n", ptr)
}


func bar(a [100]int) {
var b [1000]int
baz(b)
}


func baz(a [1000]int) {}

Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.