Skip to content

Commit 153bbbc

Browse files
authored
fix(jsonparser): Fix possible JSON log line corruption caused by json parser on query path (#18056)
This PR fixes two bugs in the json parser: 1. A bug that caused the original log line to be mutated by the json parser when the extracted field was a nested field and collided with an already existing label name, such as in line `{"app":{"id": 123, "name": "bar"}}` and label `app_name="foo"`. 2. A bug that caused a wrong json path for the extracted field in the same conditions as 1, such as `[]string{"app", "name_extracted"}` for `app_name`, instead of `[]string{"app", "name"}`. Signed-off-by: Christian Haudum <[email protected]>
1 parent 755e9fc commit 153bbbc

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

pkg/logql/log/parser.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ func (j *JSONParser) parseLabelValue(key, value []byte, dataType jsonparser.Valu
170170
sanitized := j.buildSanitizedPrefixFromBuffer()
171171
keyString, ok := j.keys.Get(sanitized, func() (string, bool) {
172172
if j.lbs.BaseHas(string(sanitized)) {
173-
j.prefixBuffer[prefixLen] = append(key, duplicateSuffix...)
173+
j.prefixBuffer[prefixLen] = make([]byte, 0, len(key)+len(duplicateSuffix))
174+
j.prefixBuffer[prefixLen] = append(j.prefixBuffer[prefixLen], key...)
175+
j.prefixBuffer[prefixLen] = append(j.prefixBuffer[prefixLen], duplicateSuffix...)
174176
}
175177

176178
keyPrefix := j.buildSanitizedPrefixFromBuffer()
@@ -182,8 +184,9 @@ func (j *JSONParser) parseLabelValue(key, value []byte, dataType jsonparser.Valu
182184
})
183185

184186
if j.captureJSONPath {
185-
jsonPath := j.buildJSONPathFromPrefixBuffer()
186-
j.lbs.SetJSONPath(keyString, jsonPath)
187+
if jsonPath := j.buildJSONPathFromPrefixBuffer(); len(jsonPath) > 0 {
188+
j.lbs.SetJSONPath(keyString, jsonPath)
189+
}
187190
}
188191

189192
// reset the prefix position
@@ -217,10 +220,20 @@ func (j *JSONParser) buildSanitizedPrefixFromBuffer() []byte {
217220
return j.sanitizedPrefixBuffer
218221
}
219222

223+
// buildJSONPathFromPrefixBuffer constructs the JSON path from the accumulated prefix buffer.
224+
// It returns a slice of strings representing each segment of the JSON path.
225+
// If the prefix buffer is empty, it returns nil.
226+
// The function also removes any "_extracted" suffix from "duplicate" fields.
220227
func (j *JSONParser) buildJSONPathFromPrefixBuffer() []string {
228+
if len(j.prefixBuffer) == 0 {
229+
return nil
230+
}
231+
221232
jsonPath := make([]string, 0, len(j.prefixBuffer))
222233
for _, part := range j.prefixBuffer {
223234
partStr := unsafe.String(unsafe.SliceData(part), len(part)) // #nosec G103 -- we know the string is not mutated
235+
// Trim _extracted suffix if the extracted field was a duplicate field
236+
partStr = strings.TrimSuffix(partStr, duplicateSuffix)
224237
jsonPath = append(jsonPath, partStr)
225238
}
226239

pkg/logql/log/parser_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ func Test_jsonParser_Parse(t *testing.T) {
3636
},
3737
NoParserHints(),
3838
},
39+
{
40+
"multi depth with duplicates",
41+
[]byte(`{"app":"bar","namespace":"prod","pod":{"uuid":"bar","deployment":{"ref":"foobar"}}}`),
42+
labels.FromStrings("app", "foo",
43+
"pod_uuid", "foo",
44+
),
45+
labels.FromStrings("app", "foo",
46+
"app_extracted", "bar",
47+
"namespace", "prod",
48+
"pod_uuid", "foo",
49+
"pod_uuid_extracted", "bar",
50+
"pod_deployment_ref", "foobar",
51+
),
52+
map[string][]string{
53+
"app_extracted": {"app"},
54+
"namespace": {"namespace"},
55+
"pod_uuid_extracted": {"pod", "uuid"},
56+
"pod_deployment_ref": {"pod", "deployment", "ref"},
57+
},
58+
NoParserHints(),
59+
},
3960
{"numeric",
4061
[]byte(`{"counter":1, "price": {"_net_":5.56909}}`),
4162
labels.EmptyLabels(),
@@ -193,10 +214,14 @@ func Test_jsonParser_Parse(t *testing.T) {
193214
for _, tt := range tests {
194215
j := NewJSONParser(true)
195216
t.Run(tt.name, func(t *testing.T) {
217+
origLine := string(tt.line)
218+
196219
b := NewBaseLabelsBuilderWithGrouping(nil, tt.hints, false, false).ForLabels(tt.lbs, tt.lbs.Hash())
197220
b.Reset()
198221
_, _ = j.Process(0, tt.line, b)
199-
require.Equal(t, tt.want, b.LabelsResult().Labels())
222+
labels := b.LabelsResult().Labels()
223+
require.Equal(t, origLine, string(tt.line), "original log line was modified")
224+
require.Equal(t, tt.want, labels)
200225

201226
// Check JSON paths if provided
202227
if len(tt.wantJSONPath) > 0 {

0 commit comments

Comments
 (0)