golang cgo: libevent handler values are set to null during execution - go

i'm working on porting this C API in go https://github.com/shammash/vde3, the library has is own event loop that use libevent, i'm using CGO.
the library require a full vde_event_handler that is composed this way
{event_add = 0x7fffe4de0db0, event_del = 0xc2000123a8, timeout_add = 0xc200000090, timeout_del = 0xc200010400}
a struct with a series of pointers to functions
during execution this field are set to NULL and i can't understand why, i think it may be the go garbage collector that (for some reason) find the reference dandling and remove them, but this shouldn't be the case
this is the incriminated function https://github.com/kurojishi/govde3/blob/master/govde.go#L23
func createNewEventHandler() *C.vde_event_handler {
var libevent_eh C.vde_event_handler
C.event_base_new()
return &libevent_eh
}
and here is a gdb log
(gdb) p *libevenet_eh
No symbol "libevenet_eh" in current context.
(gdb) p *libevent_eh
$1 = {event_add = 0x7fffe0000900, event_del = 0x30302e3028, timeout_add = 0x65736c6166, timeout_del = 0x0}
(gdb) info locals
libevent_eh = 0xc200000098
err = {__methods = 0x0, __object = 0x0}
(gdb) n
Breakpoint 1, govde.createNewEventHandler ()
at /home/kurojishi/golang/src/github.com/kurojishi/govde3/govde.go:23
23 func createNewEventHandler() C.vde_event_handler {
(gdb) info locals
$ret11 = {event_add = 0x7fffe4de0db0, event_del = 0xc2000123a8, timeout_add = 0xc200000090, timeout_del = 0xc200010400}
(gdb) n
Breakpoint 2, govde.createNewEventHandler ()
at /home/kurojishi/golang/src/github.com/kurojishi/govde3/govde.go:24
24 var libevent_eh C.vde_event_handler
(gdb) info locals
libevent_eh = {event_add = 0x0, event_del = 0x3, timeout_add = 0x7fffe4de0f8f, timeout_del = 0x7fffe4de0f8f}
$ret11 = {event_add = 0x0, event_del = 0x0, timeout_add = 0x0, timeout_del = 0x0}

You are allocating a new event handler in Go in createNewEventHandler, passing it to the C code in VdeContext.Init, and then dropping the pointer. The effect is that sometime after VdeContext.Init returns, the Go garbage collector will collect the event handler structure, even though the C code still has a pointer to it. The code will be left holding a pointer to memory that will change unpredictably.
When you allocate memory in Go and pass a pointer to C, you must keep the pointer alive in Go for as long as the C code needs to reference it.

Related

cannot inspect "if let" variables in gdb

I'm having trouble inspecting variables set by if-let statements, for instance the following main.rs in a freshly created cargo project named "iflet-rust":
#[derive(Debug)]
struct Stuff;
fn maybe() -> Option<Stuff> {
Some(Stuff {})
}
fn main() {
if let Some(first) = maybe() {
println!("first {:?}", first); // line 8, cannot inspect 'first' here
}
let maybe_stuff = maybe();
if maybe_stuff.is_some() {
let second = maybe_stuff.unwrap();
println!("second {:?}", second); // no problem here
}
}
Then running cargo build and rust-gdb target/debug/iflet-rust
Reading symbols from target/debug/iflet-rust...
(gdb) b main.rs:8
Breakpoint 1 at 0x83f1: file src/main.rs, line 8.
(gdb) b main.rs:14
Breakpoint 2 at 0x848b: file src/main.rs, line 14.
(gdb) r
Starting program: iflet-rust/target/debug/iflet-rust
Breakpoint 1, iflet_rust::main () at src/main.rs:8
8 println!("first {:?}", first); // cannot inspect 'first' here
(gdb) p first
No symbol 'first' in current context
(gdb) info locals
No locals.
(gdb) c
Continuing.
first Stuff
Breakpoint 2, iflet_rust::main () at src/main.rs:14
14 println!("second {:?}", second); // no problem here
(gdb) info locals
second = iflet_rust::Stuff
maybe_stuff = core::option::Option::Some
Is this a know limitation or am I missing something?
It was an issue in the compiler, updating to Rust from 1.60 to 1.63 fixes the problem.
I shouldn't have limited my search to open issues, unfortunate timing.
See https://github.com/rust-lang/rust/issues/97799

Why a byte buffer from CGO can reads correctly but write fault?

I am using this library https://github.com/billziss-gh/cgofuse, and some interfaces need to be implemented,one of them looks like this:
func (self *Memfs) Write(path string, buff []byte, ofst int64, fh uint64) (n int) {
defer trace(path, buff, ofst, fh)(&n)
defer self.synchronize()()
node := self.getNode(path, fh)
if nil == node {
return -fuse.ENOENT
}
endofst := ofst + int64(len(buff))
if endofst > node.stat.Size {
node.data = resize(node.data, endofst, true)
node.stat.Size = endofst
}
fmt.Println("len(buff) = ", len(buff)) // (1)
fmt.Println("cap(buff) = ", cap(buff)) // (2)
fmt.Println("buff[0] = ", buff[0]) // (3)
buff[0] = 1 // (4)
n = copy(node.data[ofst:endofst], buff)
tmsp := fuse.Now()
node.stat.Ctim = tmsp
node.stat.Mtim = tmsp
return
}
This is a file system, Write is called when writing to a file. I added (1), (2), (3), (4) in the above code, but it was wrong at (4). The error stack is as follows:
unexpected fault address 0x116e6c60390
fatal error: fault
[signal 0xc0000005 code=0x1 addr=0x116e6c60390 pc=0xca7dad]
goroutine 17 [running, locked to thread]:
runtime.throw(0xcf373f, 0x5)
D:/Scoop/apps/go/current/src/runtime/panic.go:1117 +0x79 fp=0xc000033bc8 sp=0xc000033b98 pc=0xc18db9
runtime.sigpanic()
D:/Scoop/apps/go/current/src/runtime/signal_windows.go:245 +0x2d6 fp=0xc000033c20 sp=0xc000033bc8 pc=0xc2b7d6
main.(*Memfs).Write(0xc00001e400, 0xc00000a2b0, 0x9, 0x116e6c60390, 0x40000, 0x40000000, 0x0, 0x2, 0x9)
D:/code/go/LiangFs/tool/memfs.go:310 +0x4cd fp=0xc000033de0 sp=0xc000033c20 pc=0xca7dad
github.com/billziss-gh/cgofuse/fuse.hostWrite(0x116e00d1480, 0x116e6c60390, 0x40000, 0x0, 0x1518fff7c8, 0x0)
D:/go/pkg/mod/github.com/billziss-gh/cgofuse#v1.5.0/fuse/host.go:255 +0x102 fp=0xc000033e60 sp=0xc000033de0 pc=0xc9c282
github.com/billziss-gh/cgofuse/fuse.go_hostWrite(...)
D:/go/pkg/mod/github.com/billziss-gh/cgofuse#v1.5.0/fuse/host_cgo.go:911
_cgoexp_12ef5be0dd8c_go_hostWrite(0x1518fff710)
_cgo_gotypes.go:738 +0x59 fp=0xc000033ea0 sp=0xc000033e60 pc=0xca2919
runtime.cgocallbackg1(0xca28c0, 0x1518fff710, 0x0)
D:/Scoop/apps/go/current/src/runtime/cgocall.go:292 +0x19a fp=0xc000033f40 sp=0xc000033ea0 pc=0xbe4c5a
runtime.cgocallbackg(0xca28c0, 0x1518fff710, 0x0)
D:/Scoop/apps/go/current/src/runtime/cgocall.go:228 +0xfc fp=0xc000033fb8 sp=0xc000033f40 pc=0xbe49bc
runtime.cgocallback(0x0, 0x0, 0x0)
D:/Scoop/apps/go/current/src/runtime/asm_amd64.s:788 +0xc0 fp=0xc000033fe0 sp=0xc000033fb8 pc=0xc48bc0
runtime.goexit()
D:/Scoop/apps/go/current/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc000033fe8 sp=0xc000033fe0 pc=0xc48ea1
goroutine 1 [syscall]:
github.com/billziss-gh/cgofuse/fuse._Cfunc_hostMount(0x3, 0xc00001e420, 0x116e0316540, 0x0)
_cgo_gotypes.go:502 +0x4f
github.com/billziss-gh/cgofuse/fuse.c_hostMount.func1(0xc000000003, 0xc00001e420, 0x116e0316540, 0x116e0316540)
D:/go/pkg/mod/github.com/billziss-gh/cgofuse#v1.5.0/fuse/host_cgo.go:820 +0x8c
github.com/billziss-gh/cgofuse/fuse.c_hostMount(0xc000000003, 0xc00001e420, 0x116e0316540, 0xb)
D:/go/pkg/mod/github.com/billziss-gh/cgofuse#v1.5.0/fuse/host_cgo.go:820 +0x45
github.com/billziss-gh/cgofuse/fuse.(*FileSystemHost).Mount(0xc00003e040, 0xcf4632, 0xb, 0xc000034210, 0x0, 0x0, 0x1ffffff00)
D:/go/pkg/mod/github.com/billziss-gh/cgofuse#v1.5.0/fuse/host.go:704 +0x413
main.main()
D:/code/go/LiangFs/tool/memfs.go:594 +0xce
len(buff) = 262144
cap(buff) = 1073741824
buff[0] = 97
the content of buff is all 97, becasue I copy the following file to this file system:
The code comes from the example in the library https://github.com/billziss-gh/cgofuse/blob/master/examples/memfs/memfs.go , I just added the (1), (2), (3), (4) mentioned above.
My os is windows 10, go version is go1.16.7 windows/amd64.
Why does assigning a slice element make a mistake? Is it because the library uses CGO?
I am the author of both WinFsp and cgofuse and can explain what is happening here.
The buffer that you receive in Write should always be treated as a read-only buffer. Attempting to write into this buffer may result in an access violation. This is by design.
When an application issues a WriteFile request, the WinFsp kernel mode driver has to somehow transfer the data from the application to the user mode file system. The driver has a few different strategies for doing so and under certain circumstances it will choose a zero-copy technique: it will map the application buffer directly into the address space of the user mode file system, thus avoiding an expensive memory copy.
In the case of Write this zero-copy mapping will always be read-only in order to avoid the case where a user mode accidentally or maliciously writes into an application's WriteFile buffer. (Also note that when the WinFsp driver sets up this mapping, it does so in a way that ensures that there is no accidental data leakage from the application into the user mode file system either.)
Since nothing is printed after buf[0] = 1 // (4), we don't know wether the panic comes from this line of code or a line afterwards ; you have to debug further to spot where the bug lies.
Actually : the stack trace you pasted mentions LiangFs/tool/memfs.go:310.
If this line number refers to the file you linked to, this line is the return instruction in your .Write() function.
The error must be triggered by one of the two defer calls (which get executed when you return from your function).

EnumProcessModules returns 0 with error 299

I'm currently trying to enumerate all the modules of a supsended 32-bits process I've previously created from my 32-bits program, my Windows is 64-bits.
(I've already read all the other topics on that problem).
Here is the code:
let a = CreateProcessA(
"C:\\Program Files (x86)\\GlassWire\\GlassWire.exe\0".as_ptr(),
null_mut(), null_mut(), null_mut(),
false,
0x00000004,
null_mut(), null_mut(), SI, PI);
println!("{}", GetLastError());
let mut buffer: [*mut c_void;10] = [0 as *mut c_void;10];
WaitForInputIdle((*PI).hProcess as *mut c_void, -1);
let result = EnumProcessModules(
(*PI).hProcess as *mut c_void,
buffer.as_ptr() as *mut c_void,
10, null_mut());
println!("EnumProcessModules([...]) = {} - {}", result, GetLastError());
let mut index: usize = 0x0;
let mut modname: [u8;1024] = [0;1024];
while(transmute::<*mut c_void, u32>(buffer[index]) != 0x0){
GetModuleFileNameExA((*PI).hProcess as *mut c_void, buffer[index], modname.as_ptr() as *mut c_void, 1024);
println!("module: {}", std::str::from_utf8_unchecked(&modname));
modname = [0;1024];
index += 1;
}
println!("Dump: {:?}", buffer.to_vec());
I saw that I had to use WaitForInputIdle() after using CreateProcessA before enumerating modules because the process doesn't have the time to "initialize", well when I do that, whatever the process I create (it successfully creates it) the program wait forever.
And when I try to do the same on the main process by replacing all the (*PI).hProcess by GetCurrentProcess() (I removed the WintForInputIdle() line), EnumProcessModules() still returns 0 but now GetLastError() returns 998 (ERROR_NOACCESS), but the modules handles are successfully written in buffer.
So the program output:
A:\Encrypted\Temp\injector\target\i686-pc-windows-msvc\debug\main.exe // (the program)
C:\WINDOWS\SYSTEM32\ntdll.dll
Dump: [0x920000, 0x77180000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
And if I specify the path of the running program in CreateProcessA(), it will also return 0 with 299 as the windows error code.
I'm really confused...Thanks for reading, I hope I've been clear enough.
First, use the CREATE_NEW_CONSOLE or CREATE_NO_WINDOW instead of CREATE_SUSPENDED(0x00000004), as #Jonathan Potter, the suspend state will block the function WaitForInputIdle.
Second, make sure that the process is not a console application or has a message queue, otherwise WaitForInputIdle returns immediately.
Then, you can try to call SuspendThread after WaitForInputIdle to suspend the process to prevent it from ending.
EDIT:
Pay attention to the last two parameters of EnumProcessModules:
cb
The size of the lphModule array, in bytes.
lpcbNeeded
The number of bytes required to store all module handles in the
lphModule array.
Here is a good sample on msdn: Enumerating All Modules For a Process. Just replace hProcess.

Retrieving a Golang struct from a C void pointer

I have a C struct that looks something like this:
struct room {
void *reset_first;
}
I have a Golang struct that looks something like this:
type reset struct {
command string
}
Thanks to the joys of cgo, I can do the following from Go:
func someStuff(a *C.room, b *reset) {
a.reset_first = unsafe.Pointer(b)
}
and all is well
Then I attempt to retrieve it later:
func otherStuff(a *C.room) {
var r = (*reset)(a.reset_first)
fmt.Println(r.command)
}
and I end up with a segfault:
Thread 12 "test" received signal SIGSEGV, Segmentation fault.
316 fmt.Println(r.command)
but some of the values I get from inspection with gdb surprise me:
(gdb) info args
a = 0x7fffd5bc2d48
(gdb) info locals
r = 0x5
(gdb) print a
$1 = (struct main._Ctype_struct_room *) 0x7fffd5bc2d48
(gdb) print b
$2 = (struct main.reset *) 0x5
(gdb) print *a
3 = {_type = 2, _ = "\000\000\000", reset_first = 0xc422224a80}
How does r end up as 0x5 not 0xc422224a80?
I'm not surprised that trying to dereference 5 results in a segfault, but I'm baffled as to where the 5 came from!

Interop.SHDocVw Navigate2() method displays unwanted download box

I am writing some regression tests in WatiN and needed to make a couple POST web requests. The requests are working fine but I get an annoying dialog box asking me if I want to save the file or find a program online to open it. The line of code that is causing this is:
browser.Navigate2(ref uri, ref nflags, ref ntargetFrame,
ref dataBytes, ref headers);
Is anyone familiar with the Navigate2() method? Any idea on how to get rid of this download box?
Here is my answer:
The Navigate2() method looks like this:
HRESULT Navigate2(
VARIANT *URL,
VARIANT *Flags,
VARIANT *TargetFrameName,
VARIANT *PostData,
VARIANT *Headers
);
the flags can be defined as enum BrowserNavConstants like this:
typedef enum BrowserNavConstants {
navOpenInNewWindow = 0x1,
navNoHistory = 0x2,
navNoReadFromCache = 0x4,
navNoWriteToCache = 0x8,
navAllowAutosearch = 0x10,
navBrowserBar = 0x20,
navHyperlink = 0x40,
navEnforceRestricted = 0x80,
navNewWindowsManaged = 0x0100,
navUntrustedForDownload = 0x0200,
navTrustedForActiveX = 0x0400,
navOpenInNewTab = 0x0800,
navOpenInBackgroundTab = 0x1000,
navKeepWordWheelText = 0x2000,
navVirtualTab = 0x4000,
navBlockRedirectsXDomain = 0x8000,
navOpenNewForegroundTab = 0x10000
} BrowserNavConstants;
I used navUnstrustedForDownload and it did away with the download box. Hope this helps someone somewhere

Resources