Skip to content

SEC: Block figure.hooks from being loaded from file#31293

Closed
scottshambaugh wants to merge 1 commit intomatplotlib:mainfrom
scottshambaugh:figure_hooks
Closed

SEC: Block figure.hooks from being loaded from file#31293
scottshambaugh wants to merge 1 commit intomatplotlib:mainfrom
scottshambaugh:figure_hooks

Conversation

@scottshambaugh
Copy link
Copy Markdown
Contributor

@scottshambaugh scottshambaugh commented Mar 12, 2026

PR summary

This adds figure.hooks to a new _RCPARAMS_FROM_FILE_BLACKLIST variable which blocks specific rcparams from being loaded from style files or matplotlibrc. Programmatic use like plt.rcParams["figure.hooks"] = ... is unaffected.

The figure.hooks rcParam accepts a list of "module:callable" strings that are processed via importlib.import_module() and called on every new figure. A malicious style or matplotlibrc file combined with a python file in the working directory could load and execute that module.

This is definitely a more involved attack vector since it requires a module on PATH, so I definitely see an argument for not bothering to restrict it. But I don't see a single non-empty figure.hooks when searching github, so IMO there's no downside: https://github.com/search?q=%22figure.hooks%3A+[%22&type=code

project/
  theme.mplstyle  # figure.hooks: payload:run
  payload.py  # contains a run function that is automatically executed on every figure creation

I think the new _RCPARAMS_FROM_FILE_BLACKLIST variable is more broadly useful, and we may (?) want to revisit gating LaTeX preambles this way (#31249).

This is basically a more restrictive version of style._STYLE_BLACKLIST, which should stay on its own since it's for blocking things that aren't style changes.

AI Disclosure

Claude used for the audit, code changes made manually.

PR checklist

@scottshambaugh scottshambaugh added the Security Hardening Proactive security hardening. Existing vulnerabilities should be reported per our security policy label Mar 12, 2026
@github-actions github-actions bot added the Documentation: examples files in galleries/examples label Mar 12, 2026
@tacaswell
Copy link
Copy Markdown
Member

I'm sold on removing it from style files (it is not a "style"), but I'm less sold about dropping it from all config files.

Once you can drop arbitrary files on disk the jig is up. Rather than go through 'figure.hooks', drop a copy of cycler.py with a bit of extra code at the bottom (the whole cycler library is 581 lines of code in a single file).

https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31283 (and similar writing from the same author) has shaped my thinking on this. If the pre-requisite for getting code execution is the ability to write files as the user and trick them into running a program in our directory, then we already have as high or higher privileges than we can get via this mechanism.

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

scottshambaugh commented Mar 13, 2026

I tend to agree in general (and in this case because you'd need an extra poisoned python file).

But I would push back on this for files which appear benign at first look. It's non-obvious that style files or matplotlibrc can cause code to execute, rather than merely setting flags or default values. I think the reasonable implicit assumption here is that style configurations are safe to pass around, and both humans and automated checkers could easily miss the security implications.

A more restrictive position that might make sense is to not autoload matplotlibrc at all, and require the user to call a function to load a non-default one.

@anntzer
Copy link
Copy Markdown
Contributor

anntzer commented Mar 13, 2026

I would definitely oppose such a change (in matplotlibrc files -- no opinion about style files).

The fact that no use has been found on github suggests that we didn't really advertise the feature really well after introducing it (relatively recently) in #22316. However the feature can definitely be convenient to have at a global level (rather than being something you need to activate explicitly in every script); I uploaded my personal use cases to https://github.com/anntzer/mpl-azhooks (and mplcursors' "activation by environment variable" also benefits from it, see https://mplcursors.readthedocs.io/en/stable/#activation-by-environment-variable).

Moreover, this change would only make sense if you also disallowed setting the backend in matplotlibrc files as well (which seems excessive to me), because 1) loading third-party backend modules (obviously) runs arbitrary python code, and 2) it is possible to write a "shim" backend module that starts by running arbitrary user-provided hooks (e.g., update "forbidden" rcParams) before loading and replacing itself by the real backend module (a quite hackish technique I had locally tried before having real figure hooks). (If this really goes through I guess that locally I'll switch to setting the MPLBACKEND envvar to "module://shim_backend" and do everything from there, but that's really jumping through hoops...)

Copy link
Copy Markdown
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

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

Disagree on restriction to matplotlibrc files, per the above.

@timhoffm
Copy link
Copy Markdown
Member

Maybe a somewhat radical idea: Can we deactivate loading any matplotlibrc by default? The argument would be that the behavior is completely fixed and not dependent on some global state. Processing a matplotlibrc would be opt-in (either through an environment variable or in-code). This is conceptually similar to forcing a backend via MPLBACKEND or matplotlib.use.

Users activating this would explicitly opt-into potentially dangerous behavior.

@anntzer
Copy link
Copy Markdown
Contributor

anntzer commented Mar 13, 2026

I could agree with not loading matplotlibrc, except if specified by the (already existing) MATPLOTLIBRC environment variable.

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

I think not loading a custom matplotlibrc by default is the more durable guard here, and closes this attack surface rather than us playing whack-a-mole with params that may or may not have side effects. Style files are explicitly opt-in, so I'm not as worried about those. I'll close this and open a separate issue for that.

@scottshambaugh
Copy link
Copy Markdown
Contributor Author

Issue here: #31346

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

Labels

Documentation: examples files in galleries/examples Security Hardening Proactive security hardening. Existing vulnerabilities should be reported per our security policy topic: rcparams topic: styles

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants