Incorrect mapping of c.s.j.platform.unix.solaris.LibKstat.KstatCtl
还没有人认领这个 Issue。
评估
调研方向
从 com/sun/jna/platform/unix/solaris/LibKstat.java 开始:KstatCtl.kc_chain 字段必须改为 Pointer 并提供 chain() 访问器,Kstat 需要显式的无参构造函数以及 Pointer 构造函数(与同文件中的 KstatNamed/KstatIO 保持一致),这样 next() 才能去掉它的 useMemory/read 配对。编译 issue 中的 Probe 复现程序,以确认 sizeof(KstatCtl) 在修改前后从 200 降到 24。完成的含义是结构体布局与 <kstat.h> 匹配,不再存在任何字段的 over-read/over-write,且维护者已就公开字段类型变更的 deprecation-vs-rename 方案达成一致。
由索引模型根据 Issue 内容生成。
描述
LibKstat.KstatCtl declares the kstat chain as an inline Structure where C declares a pointer to one:
@FieldOrder({"kc_chain_id", "kc_chain", "kc_kd"})
class KstatCtl extends Structure {
public int kc_chain_id;
public Kstat kc_chain; // real type: kstat_t *
public int kc_kd;
}
The real type, from <kstat.h> (identical on illumos and Oracle Solaris):
typedef struct kstat_ctl {
kid_t kc_chain_id;
kstat_t *kc_chain;
int kc_kd;
} kstat_ctl_t;
JNA computes sizeof(kstat_ctl_t) as 200 bytes, where the real struct is 24:
sizeof(JNA KstatCtl) = 200 [real kstat_ctl_t = 24]
kc_chain_id @ 0
kc_chain @ 8 (184 bytes)
kc_kd @ 192
Symptom: heap corruption
kstat_open() returns a pointer to a 24-byte block it allocated. JNA wraps that pointer in a KstatCtl and autoRead()s it (Function.java:450), reading 176 bytes past the end. Worse, a Structure passed by reference calls autoWrite() before every call (Function.java:535), so each
kstat_chain_update(kc)kstat_lookup(kc, …)kstat_read(kc, ksp, …)kstat_close(kc)
writes 176 bytes past the end of that 24-byte allocation.
The payload is the snapshot captured by the previous call's autoRead, so the effect is to revert 176 bytes of neighboring heap to their contents at the time of the last kstat call. A caller that keeps one kstat_ctl_t open for the process lifetime (the documented usage) reverts that window on every query, undoing arbitrary amounts of unrelated allocator activity.
Observed impact
OSHI hit this as an intermittent JVM crash on OpenIndiana at roughly a 15% rate per CI run. It only became diagnosable after the test runs were switched to libumem (LD_PRELOAD_64=libumem.so.1); under libc malloc the same corruption had been silently producing unexplained faults (at a much lower rate).
Reproducer
The defect is in JNA's computed layout, so it can be measured on any platform by mirroring the declarations (instantiating LibKstat.KstatCtl directly triggers Native.load("kstat") in the interface's static initializer, so the mirror is needed off-Solaris):
import com.sun.jna.*;
import com.sun.jna.Structure.FieldOrder;
public class Probe {
static final int KSTAT_STRLEN = 31;
// Verbatim copy of LibKstat.Kstat's field declarations.
@FieldOrder({ "ks_crtime", "ks_next", "ks_kid", "ks_module", "ks_resv", "ks_instance", "ks_name",
"ks_type", "ks_class", "ks_flags", "ks_data", "ks_ndata", "ks_data_size", "ks_snaptime",
"ks_update", "ks_private", "ks_snapshot", "ks_lock" })
public static class Kstat extends Structure {
public long ks_crtime;
public Pointer ks_next;
public int ks_kid;
public byte[] ks_module = new byte[KSTAT_STRLEN];
public byte ks_resv;
public int ks_instance;
public byte[] ks_name = new byte[KSTAT_STRLEN];
public byte ks_type;
public byte[] ks_class = new byte[KSTAT_STRLEN];
public byte ks_flags;
public Pointer ks_data;
public int ks_ndata;
public long ks_data_size;
public long ks_snaptime;
public int ks_update;
public Pointer ks_private;
public int ks_snapshot;
public Pointer ks_lock;
}
// Verbatim copy of LibKstat.KstatCtl's field declarations.
@FieldOrder({ "kc_chain_id", "kc_chain", "kc_kd" })
public static class KstatCtl extends Structure {
public int kc_chain_id;
public Kstat kc_chain;
public int kc_kd;
void dump() {
System.out.println("sizeof(KstatCtl) = " + size() + " (real kstat_ctl_t = 24)");
System.out.println(" kc_chain_id @ " + fieldOffset("kc_chain_id"));
System.out.println(" kc_chain @ " + fieldOffset("kc_chain"));
System.out.println(" kc_kd @ " + fieldOffset("kc_kd"));
}
}
public static void main(String[] args) {
new KstatCtl().dump();
}
}
Output on JNA 5.19.1, x86-64 / aarch64 LP64:
sizeof(KstatCtl) = 200 (real kstat_ctl_t = 24)
kc_chain_id @ 0
kc_chain @ 8
kc_kd @ 192
Suggested fix:
@FieldOrder({"kc_chain_id", "kc_chain", "kc_kd"})
class KstatCtl extends Structure {
public int kc_chain_id; // current kstat chain ID
- public Kstat kc_chain; // pointer to kstat chain
+ public Pointer kc_chain; // pointer to kstat chain
public int kc_kd; // /dev/kstat descriptor - not public interface
+
+ public Kstat chain() {
+ if (kc_chain == null) {
+ return null;
+ }
+ return new Kstat(kc_chain);
+ }
}
Kstat needs a pointer constructor for that, which KstatNamed and KstatIO in the same file already have.
Adding one means declaring the no-arg constructor explicitly, since it is currently implicit, and it lets
next() drop its hand-rolled useMemory/read pair:
class Kstat extends Structure {
…
public Pointer ks_lock; // protects this kstat's data
+ public Kstat() {
+ super();
+ }
+
+ public Kstat(Pointer p) {
+ super(p);
+ read();
+ }
+
public Kstat next() {
if (ks_next == null) {
return null;
}
- Kstat n = new Kstat();
- n.useMemory(ks_next);
- n.read();
- return n;
+ return new Kstat(ks_next);
}
}
Structure(Pointer) attaches the memory without reading it, which is why both constructors above call read()
explicitly, matching KstatNamed. The field initializers for ks_module, ks_name and ks_class run between
super(p) and the constructor body, so the arrays exist by the time read() populates them.
As a side benefit, Structure.newInstance(Kstat.class, ptr) picks up a public single-Pointer constructor when
one exists (getPointerConstructor), so adding it also makes that path read the struct instead of returning an
unpopulated instance.
Together this restores sizeof(kstat_ctl_t) to 24 and removes both the overrunning read and the overrunning write.
This changes the type of a public field, so it is source- and binary-incompatible for anyone reading kc_chain today — though reading it has never returned anything meaningful, since the bytes it decodes are whatever follows the allocation. Callers that only pass KstatCtl to the library functions, which I expect is nearly all of them, are unaffected.
I've worked around it downstream by re-declaring the four kstat_ctl_t *-taking entry points with that argument as an opaque Pointer, which also works and needs no JNA change, but the mapping should be correct for everyone.
Unrelated but adjacent
Kstat.ks_update and Kstat.ks_snapshot are declared int where C has function pointers. On LP64 this is harmless by coincidence — a 4-byte field plus 4 bytes of alignment padding occupies the same space as the pointer, so every subsequent offset and the 184-byte total are unchanged, and Structure.write() does not touch padding, so the upper half of each pointer survives a round trip. Changing them to Pointer would be offset-neutral and clearer, if you want it in the same commit. Kstat is otherwise correct at its real 184 bytes.
Environment: JNA 5.19.1; OpenIndiana Hipster (illumos), x86-64; also applies to Oracle Solaris and to SPARC
Backwards compatibility
This could cause compile failures if we alter it in-place; so we may want to deprecate the broken version and create a new version with a slightly different name. Open to ideas or preferred renaming pattern.
Happy to submit this fix (since I submitted the broken version to start with).
- 主要语言
- Java
- 星标
- 8.9k
- 派生
- 1.7k
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
java-native-access/jna 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 66/100
java-native-access/jna#1738 ·
-
难度 4/5 3-5 天 新手友好度 45/100
java-native-access/jna#1736 ·
-
难度 3/5 1-2 天 新手友好度 65/100
java-native-access/jna#1717 ·
-
Windows32Exception - The parameter is incorrect可能重新可做 @marktech0813 于 330 天前认领,目前没有进行中的 PR。 未关闭
难度 4/5 3-5 天 新手友好度 35/100
java-native-access/jna#1700 · 11 条评论 ·
-
feature request
难度 4/5 3-5 天 新手友好度 35/100
java-native-access/jna#1698 · 1 条评论 ·
查看 java-native-access/jna 的全部 Issue
相似的 Issue
-
Add Zammad可能已有人在做 @Arslan-TR 今天认领。 未关闭request
难度 2/5 1-3 小时 新手友好度 66/100
endoflife-date/endoflife.date#11298 · 1 条评论 ·
维护者通常 1 天内回复
-
bug documentation
难度 2/5 1-3 小时 新手友好度 67/100
维护者通常 1 天内回复
-
Upgrade org.apache.felix.utils to 1.11.10可能已有人在做 @stataru8 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 70/100
objectionary/eo#9329 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复