Skip to content

go: mock up for go support#19

Draft
Foxboron wants to merge 1 commit intolandlock-lsm:mainfrom
Foxboron:pull/18
Draft

go: mock up for go support#19
Foxboron wants to merge 1 commit intolandlock-lsm:mainfrom
Foxboron:pull/18

Conversation

@Foxboron
Copy link
Copy Markdown

@Foxboron Foxboron commented Apr 6, 2025

Messing with the cbindgen support for some simple go wrappers.

All WIP/POC built on top of #18

@Foxboron
Copy link
Copy Markdown
Author

Foxboron commented Apr 6, 2025

Generally, I see a slight problem with Go adoption as we will require the landlockconfig.so to be packaged by distros. I suspect that this is going to be enough friction that people are going to be reimplementing this in pure Go.

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 7, 2025

Generally, I see a slight problem with Go adoption as we will require the landlockconfig.so to be packaged by distros. I suspect that this is going to be enough friction that people are going to be reimplementing this in pure Go.

Yes, it might be a concern for some developers, but:

  • The Landlock Go library is intended for all Go programs to sandbox themselves, so most Go programs should use it instead of this Landlock configuration library.
  • This Landlock configuration library is intended for sandboxer/container tools which are a few compared to all possible Go programs, and most of them already have a configuration file format.
  • It would be a lot of effort to implement, maintain, and keep in sync a pure Go library with the reference implementation, with all the potential incompatibility or behavioral difference issues.

Container runtimes written in Go already use the seccomp library which is a binding to a shared object, so this model works in practice.

Anyway, if someone is still willing to write and maintain a similar Go library, please reach out to me and @gnoack.

I'll do my best to push this library (.so) to most Linux distros (once we have a stable version, this summer), and I'd very much appreciate help. 😉
This will be needed for other projects (e.g. systemd) to use it anyway.

@Foxboron, @rata, do you think it would help to build a static library instead of a shared object to include in a Go project?

I can update the CI to build artifacts that could easily be downloaded, verified against a hash or signature, and included in Go projects. If we go this way, we should have reproducible builds, which seems to be OK (see rust-lang/rust#129080).

@l0kod l0kod requested a review from gnoack April 7, 2025 11:33
@Foxboron
Copy link
Copy Markdown
Author

Foxboron commented Apr 7, 2025

It would be a lot of effort to implement, maintain, and keep in sync a pure Go library with the reference implementation, with all the potential incompatibility or behavioral difference issues.

If test cases are written in a way where you load a sandbox binary with a config + test case I suspect it won't be too much work? It requires a bit more effort on landing a communal test suite that works for these projects though.

I might also miss something?

Container runtimes written in Go already use the seccomp library which is a binding to a shared object, so this model works in practice.

Ah, I was trying to recall the library that did something similar when I took a stab at this. I'll take a look at the seccomp project then.

Anyway, if someone is still willing to write and maintain a similar Go library, please reach out to me and @gnoack.

I'm flagging this early as I suspect it would be unhelpful for the landlock project if a pure-Go re-implementation would land outside of the landlock-lsm organization. I'm personally up for trying the .so approach, and then we can reconsider if there is a demand for a pure-Go implementation.

I'll do my best to push this library (.so) to most Linux distros (once we have a stable version, this summer), and I'd very much appreciate help. 😉

😇

@Foxboron, @rata, do you think it would help to build a static library instead of a shared object to include in a Go project?

Good question! I'm not confident if I understand the pros/cons here to give an opinion.

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 7, 2025

It would be a lot of effort to implement, maintain, and keep in sync a pure Go library with the reference implementation, with all the potential incompatibility or behavioral difference issues.

If test cases are written in a way where you load a sandbox binary with a config + test case I suspect it won't be too much work? It requires a bit more effort on landing a communal test suite that works for these projects though.

In theory yes (see #11), but in practice a lot of tests, especially those related to compatibility, are in the Rust Landlock library, on which this config library depends. For now this is not leveraged much but that will be an important feature of the config library, especially to simplify config files and to compose them (i.e. merge several security policies from different sources and maintained by different entities, e.g. distro, dev communities, user communities). In fact, this config library should not grow too much in complexity because most of it will be handled by the Rust Landlock library. The Go Landlock library is simpler, on purpose.

Different in-theory-compatible implementations requires more work and bring more problems. I'd definitely prefer the few Landlock contributors to not duplicate their work but to focus on improvements instead (there are already two Go library implementations). There are a lot of useful and interesting things to do! 😃

Anyway, if someone is still willing to write and maintain a similar Go library, please reach out to me and @gnoack.

I'm flagging this early as I suspect it would be unhelpful for the landlock project if a pure-Go re-implementation would land outside of the landlock-lsm organization. I'm personally up for trying the .so approach, and then we can reconsider if there is a demand for a pure-Go implementation.

Yes, let's first design a first full-feature implementation.

@Foxboron
Copy link
Copy Markdown
Author

Where should the library live? I think ./go would be reasonable?

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 10, 2025

Where should the library live? I think ./go would be reasonable?

I updated #18 and moved the code to ./ffi/c. Are there Go conventions or would ./ffi/go work fine?

@Foxboron
Copy link
Copy Markdown
Author

The path becomes the import path. So ./ffi/go would be github.com/landlockconfig/ffi/go as the importable module. I suspect it would look a bit weird?

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 10, 2025

Indeed. Let's go for ./go! I guess it would make more sense to move the ./ffi/c to the root too.

@gnoack
Copy link
Copy Markdown
Member

gnoack commented Apr 13, 2025

Thanks for sending these patches!

At first glance, without a deeper look, these points stand out to me:

  • Does this break non-Cgo builds (and therefore cross-compilation)? In my understanding there are legitimate programs that get built without Cgo.
  • I would be open to make this functionality part of the go-Landlock module, maybe in a separate package within the module (provided that it is maintainable and has a clean interface)
  • Go has a built-in JSON library, which parses to native map, string and number values. People who want to "embed" Landlock policies in other configuration files are likely to already get the parsing for free and have more of a need to interpret the parsed structured data.

(In fact, I could imagine that the latter argument might also apply to programs in other languages which already ship with JSON or YAML parsers today. If we re-implement these parsers, I wonder whether we are addressing the right problem with that.)

Or to say it another way, just as a vague proposal to illustrate the idea, if this was a pure from scratch Go implementation, I feel that the only interface it would need to expose would be something similar to:

func FromJSON(jsonCfg map[string]any) *landlock.Config

and that would give users the flexibility to use the JSON/YAML parser of their choice? Why does this interface look so different to that?

@Foxboron
Copy link
Copy Markdown
Author

Does this break non-Cgo builds (and therefore cross-compilation)? In my understanding there are legitimate programs that get built without Cgo.

afaik, this would force project to be build with cgo, so yes.

I would be open to make this functionality part of the go-Landlock module, maybe in a separate package within the module (provided that it is maintainable and has a clean interface)

The current goal is to provide a shim over the Rust bindings, so I don't think having "this" functionality would work. However if we end up in a situation where we are better off creating a native Go implementation that could be an option.

Go has a built-in JSON library, which parses to native map, string and number values. People who want to "embed" Landlock policies in other configuration files are likely to already get the parsing for free and have more of a need to interpret the parsed structured data.

Sure, but I don't think this is easily done as we are dealing with cbindgen shims around the Rust library. This would probably make more sense in a native implementation scenario.

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 14, 2025

  • I would be open to make this functionality part of the go-Landlock module, maybe in a separate package within the module (provided that it is maintainable and has a clean interface)

The landlockconfig_build_ruleset() returns a ruleset FD, which could be wrapped by a higher level Go (or other language) interface usable by the Go Landlock library e.g., to add new rules and enforce the ruleset. I think we should rely on the Go Landlock library to use the ruleset FD instead of re-implementing the psx and system calls, which is already done with the imported AllThreadsLandlockRestrictSelf() and similar helpers.

  • Go has a built-in JSON library, which parses to native map, string and number values. People who want to "embed" Landlock policies in other configuration files are likely to already get the parsing for free and have more of a need to interpret the parsed structured data.

Yes, I definitely agree that we should be able to pass a chunk of JSON to the library and I'm working on another set of helpers to take a buffer as input (instead of a file descriptor).

Parsing a generic JSON and mapping it to a specific schema is not the same thing though. If we "deduplicate" the JSON parsing, then we'd need to pass a set of Go objects to the landlockconfig library, which would be a nightmare to re-parse in the FFI layer to translate to Rust object and would defeat the simplicity of just dealing with text strings, sharing implementation/tests, and combining efforts...

See my point about re-implementing the same semantic in all languages: #19 (comment)
Do you agree?

(In fact, I could imagine that the latter argument might also apply to programs in other languages which already ship with JSON or YAML parsers today.
If we re-implement these parsers, I wonder whether we are addressing the right problem with that.)

A generic JSON format is only a base to build a specific parser on top of it. The complexity is in the part that interpret/make sense of the configuration, not just the syntax. I agree that we should not re-implement parsers, which means we should only implement the Landlock Config parser in this library and expose a proper interface for other languages to use. Extracting a chunk of JSON from an already parsed file should be easy to transform to text and pass to a landlockconfig_parse_json_buffer() helper (wrapped by a higher level Go helper).

I'm not convinced we should support a wide variety of configuration syntax such as YAML. I selected two which are useful for different use cases: JSON and TOML (see explanation in README.md). And BTW, these two configuration formats (for Landlock Config) are not just automatic conversions but tailored to its specificities (e.g. the field names may not be exactly the same).

Or to say it another way, just as a vague proposal to illustrate the idea, if this was a pure from scratch Go implementation, I feel that the only interface it would need to expose would be something similar to:

func FromJSON(jsonCfg map[string]any) *landlock.Config

and that would give users the flexibility to use the JSON/YAML parser of their choice? Why does this interface look so different to that?

For now, because the provided helpers only take a file descriptor as argument, which would be useful for C bindings and languages that don't have easy to use JSON/TOML parsers.

I think something like this FromJSON interface would make sense because Go has native support for JSON in its standard library. However, about the proposed interface, I'm wondering how a tree of unknown value types would be handled.
Anyway, the upcoming update to #18 will bring a new set of helpers that would help to implement this Go interface.

BTW, TOML is also supported, and most of its use cases would involve a full file (i.e. no TOML chunks), which means that a Go program would not need to import/deal with a (generic) Go TOML parser.

@Foxboron
Copy link
Copy Markdown
Author

Fwiw, this was just a poc to check that we can use the existing setup in the repository. I'll wait for the C/C++ bindings to be merged before fully picking this up. Unless we want to test a bit more.

@rata
Copy link
Copy Markdown

rata commented Apr 14, 2025

@Foxboron, @rata, do you think it would help to build a static library instead of a shared object to include in a Go project?

Do you mean s shared libreary without any dynamic linking on other libraries? Yeah, the simpler it is to link, the better. It really depends on what library dependencies it has. libc is probably okay to have.

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 14, 2025

@Foxboron, @rata, do you think it would help to build a static library instead of a shared object to include in a Go project?

Do you mean s shared libreary without any dynamic linking on other libraries? Yeah, the simpler it is to link, the better. It really depends on what library dependencies it has. libc is probably okay to have.

Yes, I was talking about a .so or/and a .a file. Releases will include both artifacts (with libc dependency, but we could probably get rid of it if really needed).

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Apr 15, 2025

See the new landlockconfig_parse_json_buffer(): #18 (comment)

@rata
Copy link
Copy Markdown

rata commented Apr 15, 2025

Ohh, yeap, that sounds fine if it's shipped in distros :)

@Foxboron
Copy link
Copy Markdown
Author

@l0kod I'll take a new stab at this through easter. Thanks!

@l0kod
Copy link
Copy Markdown
Member

l0kod commented May 3, 2025

For reference, here is an example for Python bindings: https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-py

@Foxboron
Copy link
Copy Markdown
Author

  • Updated the module path
  • sandboxer binary lives under cmd/sandboxer
  • Added the shim functions for the buffer function calls

TODO:

  • Should figure out better go-esque functions
  • Need to figure out a way to build a .pc file so we can remove header magic
  • Is copying a file through a symlinked directory under ffi/ something we should do for all bindings?

@l0kod
Copy link
Copy Markdown
Member

l0kod commented May 21, 2025

FYI, the CI failure is now fixed with #36. Rebasing this PR will fix it.

Signed-off-by: Morten Linderud <morten@linderud.pw>
@l0kod
Copy link
Copy Markdown
Member

l0kod commented May 21, 2025

  • Need to figure out a way to build a .pc file so we can remove header magic

I'll work on this.

  • Is copying a file through a symlinked directory under ffi/ something we should do for all bindings?

c/landlockconfig.h is copied by build.rs only to make sure it is up-to-date (with the CI checking that). The build.rs change in this PR should not be required.

@Foxboron
Copy link
Copy Markdown
Author

I'll work on this.

Thanks!

c/landlockconfig.h is copied by build.rs only to make sure it is up-to-date (with the CI checking that). The build.rs change in this PR should not be required.

Blergh.

Then I just need to figure out how to solve the header file lookup for local development 🫠

@l0kod
Copy link
Copy Markdown
Member

l0kod commented May 21, 2025

It should be easier with #37

@Foxboron
Copy link
Copy Markdown
Author

(I'm still interested in this work. Just a tad burned out these days).

Regarding opencontainers/runtime-spec#1241 (comment)

Eventually, the rust dependency should be replaced with Go (or C).

Is it still worth focusing on these Rust bindings instead of figuring out how a Go native approach would look like? As upstream seems more eager for the pure Go approach.

@l0kod
Copy link
Copy Markdown
Member

l0kod commented Sep 23, 2025

(I'm still interested in this work. Just a tad burned out these days).

It's OK, thanks for the heads-up.

I'll let you know when the format will be stable (hopefully in a few weeks), but in the meantime your PoC is definitely useful to identify potential issues.

Regarding opencontainers/runtime-spec#1241 (comment)

Eventually, the rust dependency should be replaced with Go (or C).

Is it still worth focusing on these Rust bindings instead of figuring out how a Go native approach would look like? As upstream seems more eager for the pure Go approach.

Yes these C bindings will be useful. The shared object will be shipped by distros eventually, similarly to libseccomp.

Maintaining a Go binding is OK, but I definitely prefer to maintain a robust and very well tested (Rust) code instead of spending time to write and maintain a full Go re-implementation because it would be too much work and it doesn't look useful with a C binding anyway. LandlockConfig only implements the minimal set of interfaces to parse and interpret a security policy, but it doesn't implement the enforcement part, which is the job of native libraries.

I think the (OCI, and others) maintainers don't want the burden of managing N language specificities and different build systems, which makes sense, but it should be good once the LandlockConfig shared object is shipped by distros. I'm OK to handle the Go specificities for bindings, and to adapt this interface if needed, but help is definitely welcome!

@gnoack
Copy link
Copy Markdown
Member

gnoack commented Sep 23, 2025

Is it still worth focusing on these Rust bindings instead of figuring out how a Go native approach would look like? As upstream seems more eager for the pure Go approach.

Yes these C bindings will be useful. The shared object will be shipped by distros eventually, similarly to libseccomp.

Maintaining a Go binding is OK, but I definitely prefer to maintain a robust and very well tested (Rust) code instead of spending time to write and maintain a full Go re-implementation because it would be too much work and it doesn't look useful with a C binding anyway. LandlockConfig only implements the minimal set of interfaces to parse and interpret a security policy, but it doesn't implement the enforcement part, which is the job of native libraries.

I am not fully convinced by this. In Go, it is common to build programs fully statically without even linking to libc, and to keep them free of other language bindings. It used to be the default way of building and is still used in many places, especially where people want to ship fully self-contained binaries. (see e.g. https://dave.cheney.net/2016/01/18/cgo-is-not-go).

In doubt, we can make the "landlockconfig" aspect Cgo-only and omit it in pure Go builds, but we should be aware that that feature will then not be usable in some projects.

(FWIW, Go-Landlock itself is mentioning CGO in some places itself, but that's because it's compatible with that compilation mode. If you compile the library without CGO, it does the exact same thing, with the exception that it does not need to resort to libpsx to invoke landlock_restrict_self() on all threads.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants