@@ -231,6 +231,267 @@ func TestGetAdditionalPackages(t *testing.T) {
231231 }
232232}
233233
234+ func TestParseDNSSearchDomains (t * testing.T ) {
235+ tests := []struct {
236+ name string
237+ input string
238+ expected []string
239+ wantErr bool
240+ }{
241+ // Valid single domain
242+ {
243+ name : "single valid domain" ,
244+ input : "example.com" ,
245+ expected : []string {"example.com" },
246+ },
247+ // Valid multiple domains - comma separated
248+ {
249+ name : "comma separated domains" ,
250+ input : "example.com,test.org" ,
251+ expected : []string {"example.com" , "test.org" },
252+ },
253+ // Multiple commas collapsed
254+ {
255+ name : "multiple commas collapsed" ,
256+ input : "a.com,,b.org" ,
257+ expected : []string {"a.com" , "b.org" },
258+ },
259+ // Comma with spaces around domains (trimmed)
260+ {
261+ name : "comma with spaces trimmed" ,
262+ input : "a.com, b.org , c.net" ,
263+ expected : []string {"a.com" , "b.org" , "c.net" },
264+ },
265+ // Hyphenated domain
266+ {
267+ name : "hyphenated domain" ,
268+ input : "my-domain.example.com" ,
269+ expected : []string {"my-domain.example.com" },
270+ },
271+ // Nested subdomains
272+ {
273+ name : "nested subdomains" ,
274+ input : "a.b.c.d.example.com" ,
275+ expected : []string {"a.b.c.d.example.com" },
276+ },
277+ // Numeric domain parts
278+ {
279+ name : "numeric domain parts" ,
280+ input : "123.example.com" ,
281+ expected : []string {"123.example.com" },
282+ },
283+ // Empty input
284+ {
285+ name : "empty string" ,
286+ input : "" ,
287+ wantErr : true ,
288+ },
289+ // Only whitespace
290+ {
291+ name : "only whitespace" ,
292+ input : " " ,
293+ wantErr : true ,
294+ },
295+ // Only commas
296+ {
297+ name : "only commas" ,
298+ input : ",,," ,
299+ wantErr : true ,
300+ },
301+ // Space-separated domains (not allowed)
302+ {
303+ name : "space separated domains rejected" ,
304+ input : "example.com test.org" ,
305+ wantErr : true ,
306+ },
307+ // Newline in domain (not allowed)
308+ {
309+ name : "newline rejected" ,
310+ input : "foo\n bar" ,
311+ wantErr : true ,
312+ },
313+ // Tab in domain (not allowed)
314+ {
315+ name : "tab rejected" ,
316+ input : "foo\t bar" ,
317+ wantErr : true ,
318+ },
319+ // Injection with equals sign (netdev option injection)
320+ {
321+ name : "injection attempt with equals" ,
322+ input : "evil=value" ,
323+ wantErr : true ,
324+ },
325+ // Injection with hostfwd attempt
326+ {
327+ name : "hostfwd injection attempt" ,
328+ input : "foo,hostfwd=tcp::8080-:22" ,
329+ wantErr : true ,
330+ },
331+ // Injection with colon
332+ {
333+ name : "colon injection (port-like)" ,
334+ input : "domain:8080" ,
335+ wantErr : true ,
336+ },
337+ // Semicolon injection (command separator)
338+ {
339+ name : "semicolon injection" ,
340+ input : "foo;rm -rf /" ,
341+ wantErr : true ,
342+ },
343+ // Pipe injection
344+ {
345+ name : "pipe injection" ,
346+ input : "foo|cat /etc/passwd" ,
347+ wantErr : true ,
348+ },
349+ // Backtick injection
350+ {
351+ name : "backtick injection" ,
352+ input : "foo`whoami`" ,
353+ wantErr : true ,
354+ },
355+ // Dollar sign injection
356+ {
357+ name : "dollar sign injection" ,
358+ input : "foo$HOME" ,
359+ wantErr : true ,
360+ },
361+ // Quote injection
362+ {
363+ name : "double quote injection" ,
364+ input : `foo"bar` ,
365+ wantErr : true ,
366+ },
367+ // Single quote injection
368+ {
369+ name : "single quote injection" ,
370+ input : "foo'bar" ,
371+ wantErr : true ,
372+ },
373+ // Ampersand injection
374+ {
375+ name : "ampersand injection" ,
376+ input : "foo&bar" ,
377+ wantErr : true ,
378+ },
379+ // Parentheses injection
380+ {
381+ name : "parentheses injection" ,
382+ input : "foo(bar)" ,
383+ wantErr : true ,
384+ },
385+ // Bracket injection
386+ {
387+ name : "bracket injection" ,
388+ input : "foo[bar]" ,
389+ wantErr : true ,
390+ },
391+ // Brace injection
392+ {
393+ name : "brace injection" ,
394+ input : "foo{bar}" ,
395+ wantErr : true ,
396+ },
397+ // Angle bracket injection
398+ {
399+ name : "angle bracket injection" ,
400+ input : "foo<bar>" ,
401+ wantErr : true ,
402+ },
403+ // Backslash injection
404+ {
405+ name : "backslash injection" ,
406+ input : "foo\\ bar" ,
407+ wantErr : true ,
408+ },
409+ // Forward slash (path-like)
410+ {
411+ name : "forward slash injection" ,
412+ input : "foo/bar" ,
413+ wantErr : true ,
414+ },
415+ // One valid, one invalid domain
416+ {
417+ name : "mixed valid and invalid domains" ,
418+ input : "good.com,evil=bad" ,
419+ wantErr : true ,
420+ },
421+ // QEMU dnssearch option injection attempt
422+ {
423+ name : "dnssearch option injection" ,
424+ input : "foo,dnssearch=evil.com" ,
425+ wantErr : true ,
426+ },
427+ }
428+
429+ for _ , tt := range tests {
430+ t .Run (tt .name , func (t * testing.T ) {
431+ result , err := parseDNSSearchDomains (tt .input )
432+
433+ if tt .wantErr {
434+ if err == nil {
435+ t .Errorf ("parseDNSSearchDomains(%q) expected error, got nil with result %v" , tt .input , result )
436+ }
437+ return
438+ }
439+
440+ if err != nil {
441+ t .Errorf ("parseDNSSearchDomains(%q) unexpected %v" , tt .input , err )
442+ return
443+ }
444+
445+ if len (result ) != len (tt .expected ) {
446+ t .Errorf ("parseDNSSearchDomains(%q) returned %d domains, expected %d: got %v, want %v" ,
447+ tt .input , len (result ), len (tt .expected ), result , tt .expected )
448+ return
449+ }
450+
451+ for i , domain := range result {
452+ if domain != tt .expected [i ] {
453+ t .Errorf ("parseDNSSearchDomains(%q)[%d] = %q, expected %q" ,
454+ tt .input , i , domain , tt .expected [i ])
455+ }
456+ }
457+ })
458+ }
459+ }
460+
461+ func TestBuildDNSSearchNetdevArgs (t * testing.T ) {
462+ tests := []struct {
463+ name string
464+ domains []string
465+ expected string
466+ }{
467+ {
468+ name : "empty domains" ,
469+ domains : nil ,
470+ expected : "" ,
471+ },
472+ {
473+ name : "single domain" ,
474+ domains : []string {"example.com" },
475+ expected : ",dnssearch=example.com" ,
476+ },
477+ {
478+ name : "multiple domains" ,
479+ domains : []string {"a.com" , "b.org" , "c.net" },
480+ expected : ",dnssearch=a.com,dnssearch=b.org,dnssearch=c.net" ,
481+ },
482+ }
483+
484+ for _ , tt := range tests {
485+ t .Run (tt .name , func (t * testing.T ) {
486+ result := buildDNSSearchNetdevArgs (tt .domains )
487+ if result != tt .expected {
488+ t .Errorf ("buildDNSSearchNetdevArgs(%v) = %q, expected %q" ,
489+ tt .domains , result , tt .expected )
490+ }
491+ })
492+ }
493+ }
494+
234495func TestGetPackageCacheSuffix (t * testing.T ) {
235496 tests := []struct {
236497 name string
0 commit comments