Skip to content

Commit 9f5e337

Browse files
committed
Disallow logging.conf from elsewhere than the app folder
1 parent b77f3f9 commit 9f5e337

3 files changed

Lines changed: 102 additions & 3 deletions

File tree

‎splunklib/searchcommands/environment.py‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ def configure_logging(logger_name, filename=None):
9797
global _current_logging_configuration_file
9898
filename = path.realpath(filename)
9999

100+
app_root_real = path.realpath(app_root)
101+
if path.commonpath([filename, app_root_real]) != app_root_real: # pyright: ignore[reportUnknownArgumentType]
102+
raise ValueError(
103+
f'Logging configuration file "{filename}" is outside the app directory'
104+
)
105+
100106
if filename != _current_logging_configuration_file:
101107
working_directory = getcwd()
102108
chdir(app_root)
@@ -114,9 +120,21 @@ def configure_logging(logger_name, filename=None):
114120

115121
_current_logging_configuration_file = None
116122

123+
124+
def _find_app_root(app_file: str, splunk_home: str) -> str:
125+
"""Return the app root directory for a search command script."""
126+
splunk_apps_dir = path.join(splunk_home, "etc", "apps")
127+
app_relpath = path.relpath(path.abspath(app_file), splunk_apps_dir)
128+
app_dir = app_relpath.split(path.sep, 1)[0]
129+
if app_dir == path.pardir: # app_file not in $SPLUNK_HOME/etc/apps
130+
return path.dirname(path.abspath(path.dirname(app_file)))
131+
132+
return path.join(splunk_apps_dir, app_dir)
133+
134+
117135
splunk_home = path.abspath(path.join(getcwd(), environ.get("SPLUNK_HOME", "")))
118136
app_file = getattr(sys.modules["__main__"], "__file__", sys.executable)
119-
app_root = path.dirname(path.abspath(path.dirname(app_file)))
137+
app_root = _find_app_root(app_file, splunk_home)
120138

121139
splunklib_logger, logging_configuration = configure_logging("splunklib")
122140

‎tests/unit/searchcommands/test_builtin_options.py‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from splunklib.searchcommands import environment
2525
from splunklib.searchcommands.decorators import Configuration
2626
from splunklib.searchcommands.search_command import SearchCommand
27-
from tests.unit.searchcommands import package_directory, rebase_environment
27+
from tests.unit.searchcommands import rebase_environment
2828

2929

3030
# portable log level names
@@ -126,7 +126,7 @@ def test_logging_configuration(self):
126126

127127
try:
128128
command.logging_configuration = os.path.join(
129-
package_directory, "non-existent.logging.conf"
129+
os.path.dirname(os.path.realpath(__file__)), "non-existent.logging.conf"
130130
)
131131
except ValueError:
132132
pass
@@ -137,6 +137,39 @@ def test_logging_configuration(self):
137137
f"Expected ValueError, but logging_configuration={command.logging_configuration}"
138138
)
139139

140+
inside_app_root_logging_configuration = os.path.join(
141+
environment.app_root, "default", "logging.conf"
142+
)
143+
command.logging_configuration = inside_app_root_logging_configuration
144+
assert command.logging_configuration == inside_app_root_logging_configuration, (
145+
"logging_configuration should accept an absolute path inside the app directory"
146+
)
147+
148+
try:
149+
command.logging_configuration = os.path.realpath(__file__)
150+
except ValueError:
151+
pass
152+
except BaseException as e:
153+
pytest.fail(
154+
f"Expected ValueError for a path outside the app directory, but {type(e)} was raised"
155+
)
156+
else:
157+
pytest.fail(
158+
f"Expected ValueError for a path outside the app directory, but {command.logging_configuration=}"
159+
)
160+
161+
# logging_configuration raises a value error when a relative path traverses outside the app directory (RCE guard)
162+
try:
163+
command.logging_configuration = os.path.join("..", "..", "..", "__init__.py")
164+
except ValueError:
165+
pass
166+
except BaseException as e:
167+
pytest.fail(f"Expected ValueError, but {type(e)} was raised")
168+
else:
169+
pytest.fail(
170+
f"Expected ValueError, but logging_configuration={command.logging_configuration}"
171+
)
172+
140173
def test_logging_level(self):
141174
rebase_environment("app_without_logging_configuration")
142175
command = StubbedSearchCommand()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright © 2011-2026 Splunk, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"): you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import os
16+
17+
import pytest
18+
19+
from splunklib.searchcommands.environment import (
20+
_find_app_root, # pyright: ignore[reportPrivateUsage]
21+
)
22+
23+
_SPLUNK_HOME = os.path.join(os.sep, "opt", "splunk")
24+
_APPS_DIRECTORY = os.path.join(_SPLUNK_HOME, "etc", "apps")
25+
26+
27+
@pytest.mark.parametrize(
28+
("app_file_parts", "expected_app_root_parts"),
29+
[
30+
# A script located directly in the app's bin directory
31+
(("my_app", "bin", "command.py"), ("my_app",)),
32+
# A script located in a subdirectory of bin, including one that happens to be
33+
# named "bin" itself; the app root is still $SPLUNK_HOME/etc/apps/my_app
34+
(("my_app", "bin", "foo", "bin", "command.py"), ("my_app",)),
35+
],
36+
)
37+
def test_find_app_root(
38+
app_file_parts: tuple[str, ...], expected_app_root_parts: tuple[str, ...]
39+
) -> None:
40+
app_file = os.path.join(_APPS_DIRECTORY, *app_file_parts)
41+
expected_app_root = os.path.join(_APPS_DIRECTORY, *expected_app_root_parts)
42+
assert _find_app_root(app_file, _SPLUNK_HOME) == expected_app_root
43+
44+
45+
def test_find_app_root_falls_back_on_nonstandard_splunk_home() -> None:
46+
app_file = os.path.join("some", "other", "layout", "command.py")
47+
expected_app_root = os.path.dirname(os.path.abspath(os.path.dirname(app_file)))
48+
assert _find_app_root(app_file, _SPLUNK_HOME) == expected_app_root

0 commit comments

Comments
 (0)