Skip to content

How did I exported a Golang program into an Apache module

Coraza’s high level APIs are based on libmodsecurity, we use the same 5 phases and the same setters for:

  • Connection Request
  • Request Line
  • Request Headers
  • Request Body
  • Response Body
  • Logging

So technically, it should be easy to export Coraza functions to C using the same structure as libmodsecurity. With a few modifications I ended up with a working API that can be embedded inside the libmodsecurity apache connector.

  • coraza_new_waf
  • coraza_new_transaction
  • coraza_new_transaction_with_id
  • coraza_new_seclang_parser
  • coraza_intervention
  • coraza_process_connection
  • coraza_process_request_body
  • coraza_update_status_code
  • coraza_process_uri
  • coraza_add_request_header
  • coraza_process_request_headers
  • coraza_process_logging
  • coraza_append_request_body
  • coraza_add_response_header
  • coraza_append_response_body
  • coraza_process_response_body
  • coraza_process_response_headers
  • coraza_rules_add_file

We are not going to explain how it ended up kind of working but I’m going to tell you how I managed to deal with the Golang’s garbage collector and the awful unsafe package.

It makes sense to think that you could just export the unsafe.Pointer of some golang’s variable, send it to C and just bring it back to golang, something like: Create a WAF instance, now create a transaction from the void pointer casted as a Transaction; but no, it won’t work, the garbage collector will eat it, so how do we deal with that?

Mallocs in golang!, yes, you read malloc. One of the beautiful features of golang is that you don’t need to handle memory, the runtime will allocate memory and free by itself, but in this case, that feature is our enemy, and no, we shouldn’t disable it (I don’t even think it’s possible).
So let’s see how I managed to create a exportable Waf instance:

//export coraza_new_waf
func coraza_new_waf() unsafe.Pointer {
	waf := coraza.NewWaf()
	corazaMemAlloc := C.malloc(C.size_t(unsafe.Sizeof(uintptr(0))))
	a := (*[1]*coraza.Waf)(corazaMemAlloc)
	a[0] = &(*(*coraza.Waf)(unsafe.Pointer(waf)))
	return corazaMemAlloc
}

First we create the golang variable waf, then we use the C library to allocate a the space for a pointer (uintptr(0), now we create a slice of coraza.Waf pointers with fixed length and we cast it as the malloc we just made. Now we write to the first element of a the direct unsafe.Pointer to waf, finally we return the malloc as an unsafe.Pointer.
Yes, this explanation is awful but I’m sure golang is not designed to do that, when I asked for help in some golang groups people just told me “don’t do that”.

Now that we returned an unsafe.Pointer, we store it as a void in our C code and we can send it to more golang functions like coraza_new_transaction as a C.coraza_waf_t (typedef void* coraza_waf_t;)

func coraza_new_transaction(waf C.coraza_waf_t, logCb unsafe.Pointer) unsafe.Pointer {
	if waf == nil {
		panic("waf is nil")
	}
	w := *(*coraza.Waf)((*[1]*coraza.Waf)(waf)[0])

	tx := w.NewTransaction()
	txMemAlloc := C.malloc(C.size_t(unsafe.Sizeof(uintptr(0))))
	a := (*[1]*coraza.Transaction)(txMemAlloc)
	a[0] = &(*(*coraza.Transaction)(unsafe.Pointer(tx)))
	return txMemAlloc
}

Casting the waf pointer to coraza.Waf is way easier, in one line we create a coraza.Waf pointer that retrieves the first element of the pointer created by the previous malloc. Then we repeat the same process to create a transaction exportable pointer.

Final notes

Golang is not friendly when you want C programs to consume it as a library, but I still managed to do so. It doesn’t mean it is a definitive solution, I still think there might be an easier way or there might be bugs that I will encounter in the future. In the meantime, let’s just call it a victory and wait for the Apache connector.

You can review the code at: https://github.com/jptosso/coraza-cexport

Leave a Reply

Your email address will not be published. Required fields are marked *