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

inventory plugins: make wrapping variables as unsafe smarter to avoid triggering an AWX bug #8225

Merged
merged 1 commit into from
Apr 20, 2024

Conversation

felixfontein
Copy link
Collaborator

SUMMARY

Ref: #8212.

Closes #8212.

ISSUE TYPE
  • Bugfix Pull Request
COMPONENT NAME

inventory plugins

@felixfontein felixfontein added backport-6 backport-8 Automatically create a backport for the stable-8 branch labels Apr 15, 2024
@ansibullbot ansibullbot added bug This issue/PR relates to a bug cloud inventory inventory plugin plugins plugin (any type) labels Apr 15, 2024
@felixfontein felixfontein force-pushed the unsafe branch 2 times, most recently from 7d876b2 to 32d724c Compare April 15, 2024 16:23
@ansibullbot ansibullbot added needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR and removed needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR labels Apr 15, 2024
Comment on lines +32 to +39
elif isinstance(value, binary_type):
if _RE_TEMPLATE_CHARS_BYTES.search(value):
value = _make_unsafe(value)
return value
elif isinstance(value, text_type):
if _RE_TEMPLATE_CHARS.search(value):
value = _make_unsafe(value)
return value
Copy link
Contributor

@evgeni evgeni Apr 16, 2024

Choose a reason for hiding this comment

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

I was tempted to say "bleh, regex, use something in value", but a quick timeit says regex is actually faster:

import re
import timeit

binary_type = bytes
text_type = str

_RE_TEMPLATE_CHARS = re.compile(u'[{}]')
_RE_TEMPLATE_CHARS_BYTES = re.compile(b'[{}]')

def _make_unsafe(value):
    return value

def make_unsafe(value):
    if isinstance(value, binary_type):
        if _RE_TEMPLATE_CHARS_BYTES.search(value):
            value = _make_unsafe(value)
        return value
    elif isinstance(value, text_type):
        if _RE_TEMPLATE_CHARS.search(value):
            value = _make_unsafe(value)
        return value
    return value

def make_unsafe_in(value):
    if isinstance(value, binary_type):
        if any(check in value for check in [b'{{', b'}}']):
            value = _make_unsafe(value)
        return value
    if isinstance(value, text_type):
        if any(check in value for check in ['{{', '}}']):
            value = _make_unsafe(value)
        return value
    return value

print(timeit.timeit('make_unsafe("adsdfsdf{{sdfsdfsf}}aaaaa")', globals=globals()))
print(timeit.timeit('make_unsafe(b"adsdfsdf{{sdfsdfsf}}aaaaa")', globals=globals()))
print(timeit.timeit('make_unsafe_in("adsdfsdf{{sdfsdfsf}}aaaaa")', globals=globals()))
print(timeit.timeit('make_unsafe_in(b"adsdfsdf{{sdfsdfsf}}aaaaa")', globals=globals()))
% python3.12 /tmp/bench.py
0.27897425496485084
0.2632201029919088
0.6106972509878688
0.8229789500473998

of course, PyPy is a magnitude faster, but still regex wins:

% pypy3.9 /tmp/bench.py
0.026400349976029247
0.018924177973531187
0.05259729997487739
0.047663011006079614

Copy link
Contributor

@evgeni evgeni Apr 16, 2024

Choose a reason for hiding this comment

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

But once I switch from any(…) to an explicit '{{' in value or '}}' in value, things get faster (which, in retrospect, makes sense, if a or b short-cuts when a is true, while any([a, b]) does execute BOTH, and then takes any()):

% python3.12 /tmp/bench.py
0.29455614904873073
0.2644505600328557
0.10402327799238265
0.28959875099826604

% pypy3.9 /tmp/bench.py  
0.023167914012447
0.022700645960867405
0.0018618669710122049
0.0015594420256093144

Copy link
Contributor

@evgeni evgeni Apr 16, 2024

Choose a reason for hiding this comment

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

The above is btw with:

% rpm -q python3 pypy3.9
python3-3.12.2-2.fc39.x86_64
pypy3.9-7.3.15-2.3.9.fc39.x86_64

Comment on lines +18 to +19
_RE_TEMPLATE_CHARS = re.compile(u'[{}]')
_RE_TEMPLATE_CHARS_BYTES = re.compile(b'[{}]')
Copy link
Contributor

@evgeni evgeni Apr 16, 2024

Choose a reason for hiding this comment

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

I've been thinking about those character groups.
On the one hand, to be "real" Jinja, we need {{, {# or {% (and their counterparts), so the groups here also match on non-Jinja things. But then again, I wouldn't really care.
And we also do not really need to care for closing (the group above matches on either anyway).
So this could become

Suggested change
_RE_TEMPLATE_CHARS = re.compile(u'[{}]')
_RE_TEMPLATE_CHARS_BYTES = re.compile(b'[{}]')
_RE_TEMPLATE_CHARS = re.compile(u'{')
_RE_TEMPLATE_CHARS_BYTES = re.compile(b'{')

at IMHO no (significant) downside, and with the upside that the matching is faster:

% python3.12 /tmp/bench.py
0.19322030898183584
0.17506323801353574
0.09858699102187529
0.28751170000759885

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, the idea was to avoid matching things that obviously aren't templates (and can't concatenated to become templates), like IP addresses or hostnames, so that ansible_host and friends are only marked as unsafe if they contain potentially problematic things.

Whether the code should also check for } or not is 🤷. I'll probably stick to the lazy option and not change it ;-) If it turns out to be too slow we can still reduce it to a simple '{' in value check.

@evgeni
Copy link
Contributor

evgeni commented Apr 16, 2024

Overall, I think this is fine.

@felixfontein felixfontein merged commit 7fd37ea into ansible-collections:main Apr 20, 2024
132 of 133 checks passed
Copy link

patchback bot commented Apr 20, 2024

Backport to stable-6: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-6/7fd37ea247ba351b541d472cbedefc60fb98473f/pr-8225

Backported as #8244

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@felixfontein felixfontein deleted the unsafe branch April 20, 2024 07:39
patchback bot pushed a commit that referenced this pull request Apr 20, 2024
… triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)
Copy link

patchback bot commented Apr 20, 2024

Backport to stable-7: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-7/7fd37ea247ba351b541d472cbedefc60fb98473f/pr-8225

Backported as #8245

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@felixfontein
Copy link
Collaborator Author

@evgeni thanks a lot for taking a look at this!

patchback bot pushed a commit that referenced this pull request Apr 20, 2024
… triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)
Copy link

patchback bot commented Apr 20, 2024

Backport to stable-8: 💚 backport PR created

✅ Backport PR branch: patchback/backports/stable-8/7fd37ea247ba351b541d472cbedefc60fb98473f/pr-8225

Backported as #8246

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

patchback bot pushed a commit that referenced this pull request Apr 20, 2024
… triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)
felixfontein added a commit that referenced this pull request Apr 20, 2024
…ng variables as unsafe smarter to avoid triggering an AWX bug (#8245)

inventory plugins: make wrapping variables as unsafe smarter to avoid triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)

Co-authored-by: Felix Fontein <felix@fontein.de>
felixfontein added a commit that referenced this pull request Apr 20, 2024
…ng variables as unsafe smarter to avoid triggering an AWX bug (#8244)

inventory plugins: make wrapping variables as unsafe smarter to avoid triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)

Co-authored-by: Felix Fontein <felix@fontein.de>
felixfontein added a commit that referenced this pull request Apr 20, 2024
…ng variables as unsafe smarter to avoid triggering an AWX bug (#8246)

inventory plugins: make wrapping variables as unsafe smarter to avoid triggering an AWX bug (#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.

(cherry picked from commit 7fd37ea)

Co-authored-by: Felix Fontein <felix@fontein.de>
aretrosen pushed a commit to aretrosen/community.general that referenced this pull request Apr 22, 2024
… triggering an AWX bug (ansible-collections#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.
Massl123 pushed a commit to Massl123/community.general that referenced this pull request Feb 7, 2025
… triggering an AWX bug (ansible-collections#8225)

Make wrapping variables as unsafe smarter to avoid triggering an AWX bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-8 Automatically create a backport for the stable-8 branch bug This issue/PR relates to a bug cloud has_issue inventory inventory plugin plugins plugin (any type)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dynamic inventory error in Ansible AWX - Nmap Plugin
3 participants