Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict systems operations on OpenBSD #2934

Merged
merged 3 commits into from Oct 22, 2023
Merged

Conversation

klemensn
Copy link
Contributor

Use pledge(2)[0] to limit jq(1) to reading files.
It does not change files and only writes to standard output/error.
It never deals with TTY, network, process management or other subsystems.

This is to reduce jq's attack surface and potential damage.

OpenBSD is carrying a local patch[1] in its official jq port/package
since 2016. An improved version:

  • drop no longer needed "getpw" promise
    f1c4947 "Avoid getpwuid for static linking" removed getpwuid(3) usage
  • pledge before jq_init() to simplify the error path
  • use perror(3) to print errno(2)

No behaviour change in tests or real world usage observed on
OpenBSD/amd64 7.4.

0: https://man.openbsd.org/pledge.2
1: https://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/textproc/jq/patches/patch-main_c

Use pledge(2)[0] to limit jq(1) to reading files.
It does not change files and only writes to standard output/error.
It never deals with TTY, network, process management or other subsystems.

This is to reduce jq's attack surface and potential damage.

OpenBSD is carrying a local patch[1] in its official jq port/package
since 2016.  An improved version:

- drop no longer needed "getpw" promise
  f1c4947 "Avoid getpwuid for static linking" removed getpwuid(3) usage
- pledge before jq_init() to simplify the error path
- use perror(3) to print errno(2)

No behaviour change in tests or real world usage observed on
OpenBSD/amd64 7.4.

0: https://man.openbsd.org/pledge.2
1: https://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/textproc/jq/patches/patch-main_c
b82c231 "Remove -i option (jqlang#704)" removed its last usage in 2015.

Spotted while looking for code could potentially write/create/modify files.
Copy link
Member

@wader wader left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nicowilliams i think we discussed merging openbsd patches some months ago?

@klemensn
Copy link
Contributor Author

klemensn commented Oct 21, 2023

@nicowilliams i think we discussed merging openbsd patches some months ago?

FWIW, there is just one other years-old patch for the configure script to "fix" thread-local storage detection (when using clang/LLVM), but at least on amd64 using clang 13.0.0 this no longer seems to be needed.

I'll have yet to check other architectures/compiler combinations, but it seems this patch can go, i.e. this PR about pledge would be the only patch relevant to upstream/you.

emanuele6
emanuele6 previously approved these changes Oct 21, 2023
src/main.c Show resolved Hide resolved
src/main.c Outdated Show resolved Hide resolved
Otherwise `AGRS` and `program_arguments` remain allocated/unfreed in the
early (extremely unlikely) pledge(2) failure case.

Move their allocation before jq_init(), the first case of jumping to
`out` where they are cleaned up, where it also seems to logically fit
better than above between function entry, locale setup and OpenBSD
specific pledge.
Copy link
Member

@emanuele6 emanuele6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

emanuele6 added a commit to emanuele6/jq-1 that referenced this pull request Oct 21, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

I also fixed input_jq_util_input_init() not handling OOM errors by
returning NULL, and added code to make jq exit cleanly if it returns
NULL. The code base is filled with these kinds of problems, but this one
was easy to fix, so might as well fix it now...

Ref: jqlang#2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
emanuele6 added a commit to emanuele6/jq-1 that referenced this pull request Oct 21, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

I also fixed input_jq_util_input_init() not handling OOM errors by
returning NULL, and added code to make jq exit cleanly if it returns
NULL. The code base is filled with these kinds of problems, but this one
was easy to fix, so might as well fix it now...

Ref: jqlang#2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
emanuele6 added a commit to emanuele6/jq-1 that referenced this pull request Oct 21, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

I also fixed jq_util_input_init() not handling OOM errors by returning
NULL, and added code to make jq exit cleanly if it returns NULL. The
code base is filled with these kinds of problems, but this one was easy
to fix, so might as well fix it now...

Ref: jqlang#2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
@nicowilliams
Copy link
Contributor

Just FYI the dlopen branch and @leonid-s-usov's fork of that will have to interface a wee bit with this. My idea was to disallow anything that could be used to get out of the jq "sandbox" by default and require an explicit authz jq program (or policy expressed in JSON) be given as an explicit command-line option.

@klemensn
Copy link
Contributor Author

klemensn commented Oct 22, 2023

Just FYI the dlopen branch and @leonid-s-usov's fork of that will have to interface a wee bit with this. My idea was to disallow anything that could be used to get out of the jq "sandbox" by default and require an explicit authz jq program (or policy expressed in JSON) be given as an explicit command-line option.

I see over three year old, barely documented code to

My impression is that this won't land any time soon.

Not sure what you mean with "explicit authz jq program", but either way, yes, that would have to pry open the currently nicely small and secure set of promises.

I personally don't care about this until it is considered to land in jq proper.

emanuele6 added a commit to emanuele6/jq-1 that referenced this pull request Oct 22, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

I also fixed jq_util_input_init() not handling OOM errors by returning
NULL, and added code to make jq exit cleanly if it returns NULL. The
code base is filled with these kinds of problems, but this one was easy
to fix, so might as well fix it now...

Ref: jqlang#2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
@nicowilliams nicowilliams merged commit 7ab117a into jqlang:master Oct 22, 2023
28 checks passed
@nicowilliams
Copy link
Contributor

Thanks!

emanuele6 added a commit to emanuele6/jq-1 that referenced this pull request Oct 22, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

Ref: jqlang#2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
nicowilliams pushed a commit that referenced this pull request Oct 22, 2023
If jq_init() fails, goto out would try to free input_state which is
uninitialised. I initialised input_state to NULL to fix the problem.

Ref: #2934 (comment)

Reported-By: Klemens Nanni <kn@openbsd.org>
@nicowilliams
Copy link
Contributor

Not sure what you mean with "explicit authz jq program", but either way, yes, that would have to pry open the currently nicely small and secure set of promises.

If we express sandbox constraints as JSON then we could use those to drive pledges. If we express sandbox constraints as jq programs whose inputs are descriptions of actions the user's jq program wants to do and whose outputs are booleans (allowed and disallowed) then that won't work with pledges at all.

The thought occurs that it would be nice if one could use a command-line utility -let's call it pledge(1)- to pledge promises for a program to be execve()'ed by that utility. There is a problem here though: how to express the point in the exec'ed program's lifetime at which the pledges are to become active, since it often won't do to pledge maximal promises before the program has bootstrapped itself.

@klemensn
Copy link
Contributor Author

klemensn commented Oct 22, 2023

The thought occurs that it would be nice if one could use a command-line utility -let's call it pledge(1)- to pledge promises for a program to be execve()'ed by that utility. There is a problem here though: how to express the point in the exec'ed program's lifetime at which the pledges are to become active, since it often won't do to pledge maximal promises before the program has bootstrapped itself.

This is not how pledge(2) was designed, I don't expect there to be this kind of wrapper.

@emanuele6
Copy link
Member

emanuele6 commented Oct 22, 2023

@nicowilliams

For what it is worth, SerenityOS has a pledge(1) tool that works like that (originally inspired by this post): https://github.com/SerenityOS/serenity/blob/master/Userland/Utilities/pledge.cpp

Though you might think impleminting it is as simple as doing pledge(NULL, argv[1]); execv(argv[2], &argv[2]), that is actually not the case, because most programs are dynamically linked, and the dynamic loader will need a lot of permissions that you would usually want to pledge not to use. SerenityOS has to do some dynamic loader magic to delay giving up some permissions only after loading the objects.

If jq will support running external commands in the future, we will have to extend the list of promises (and may re-reduce it after compiling if they turn out to be unnecessary).
And, if jq will support calling arbitrary code with a FFI, we will have to remove pledge completely (or only pledge after compiling the jq code if it can detect that FFI is not used).
For now, jq only needs stdio rpath so we might aswell pledge to only use those.

@nicowilliams
Copy link
Contributor

Though you might think impleminting it is as simple as doing pledge(NULL, argv[1]); execv(argv[2], &argv[2]), that is actually not the case, because most programs are dynamically linked, and the dynamic loader will need a lot of permissions that you would usually want to pledge not to use. SerenityOS has to do some dynamic loader magic to delay giving up some permissions only after loading the objects.

Indeed, that's the sort of problem I was referring to by "bootstrap". If there was a int pledge_prep(const char *, const char *); and void pledge_commit(void); such that programs could just pledge_commit() and then a pledge(1) utility could prep their pledges for that time... But anyways, I'm not too interested. I would have preferred to build a pledge-like system on top of Illumos' privileges scheme, since it's a very natural fit there (it'd most be new privs that would be in the limit set by default and that one could remove from the limit set when pledging).

@emanuele6 emanuele6 added feature request lint Code cleanup; style fixes; small refactors; dead code removal; etc. labels Oct 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request lint Code cleanup; style fixes; small refactors; dead code removal; etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants