mesh-mail: mail_search's address filter never treats % or _ as a wildcard, so a literal domain filter does not leak every mail in Postgres (issue 1228, review finding 4)
This commit is contained in:
@@ -397,15 +397,19 @@ func (s *PgStore) Get(ctx context.Context, account, folder string, uid uint32) (
|
||||
|
||||
// addressFilterSQL is 1228/AC2, 1228/AC4, 1228/AC6(cc): an EXISTS over the jsonb array column (recipients or
|
||||
// copied), matched against one already-lowercased parameter either as a whole address or, with no "@", as a
|
||||
// domain (every address under it). column names a fixed set the Go code chooses, never caller input.
|
||||
// domain (every address under it): right(address, length(domain)+1) = '@'||domain, an exact substring compare,
|
||||
// not LIKE, so a filter holding "%" or "_" is never a wildcard (issue 1228, review round 1 finding 4: a filter
|
||||
// from:["%"] matched every mail in Postgres and none in memStore, since memStore's strings.HasSuffix never
|
||||
// treats "%" specially). column names a fixed set the Go code chooses, never caller input.
|
||||
func addressFilterSQL(column, param string) string {
|
||||
return fmt.Sprintf("exists (select 1 from jsonb_array_elements(%s) el where lower(el->>'address') = %s or "+
|
||||
"lower(el->>'address') like '%%@' || %s)", column, param, param)
|
||||
"right(lower(el->>'address'), length(%s) + 1) = '@' || %s)", column, param, param, param)
|
||||
}
|
||||
|
||||
// addressFilterSQLAny is 1228/AC3; 1230/AC11 ("several domains"): an EXISTS over column (sender), matched
|
||||
// against any of filters, OR'd (each already-lowercased, each either a whole address or, with no "@", a domain).
|
||||
// The whole list reaches SQL as one parameter, a text[]; never concatenated.
|
||||
// against any of filters, OR'd (each already-lowercased, each either a whole address or, with no "@", a domain),
|
||||
// the same exact substring compare as addressFilterSQL, never LIKE. The whole list reaches SQL as one
|
||||
// parameter, a text[]; never concatenated.
|
||||
func addressFilterSQLAny(column string, filters []string, param func(any) string) string {
|
||||
lowered := make([]string, len(filters))
|
||||
for i, f := range filters {
|
||||
@@ -413,7 +417,7 @@ func addressFilterSQLAny(column string, filters []string, param func(any) string
|
||||
}
|
||||
p := param(lowered)
|
||||
return fmt.Sprintf("exists (select 1 from jsonb_array_elements(%s) el, unnest(%s::text[]) f where "+
|
||||
"lower(el->>'address') = f or lower(el->>'address') like '%%@' || f)", column, p)
|
||||
"lower(el->>'address') = f or right(lower(el->>'address'), length(f) + 1) = '@' || f)", column, p)
|
||||
}
|
||||
|
||||
// wordFilterSQL is 1229/AC5, AC6(words), AC9, AC12, AC13, AC15: one condition per word of the list, each a
|
||||
|
||||
@@ -208,6 +208,30 @@ func TestSearchFiltersByCcNotTo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// 1228 (review round 1, finding 4): a domain filter of "%" or "_" is never a LIKE wildcard; it matches nobody,
|
||||
// the same in both stores (a Postgres-only bug matched every mail in Postgres, since '%@' || '%' is the
|
||||
// wildcard '%@%', while memStore's strings.HasSuffix never treats '%' specially).
|
||||
func TestSearchFiltersByDomainNeverTreatsPercentAsAWildcard(t *testing.T) {
|
||||
eachStore(t, func(t *testing.T, st Store) {
|
||||
ctx := context.Background()
|
||||
addFixtures(t, ctx, st)
|
||||
got, err := st.Search(ctx, Query{Accounts: []string{"mesh", "gmail"}, From: []string{"%"}, Limit: 50})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("from:[%%] matched %d mail(s): %v", len(got), got)
|
||||
}
|
||||
toUnderscore, err := st.Search(ctx, Query{Accounts: []string{"mesh", "gmail"}, To: "_", Limit: 50})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(toUnderscore) != 0 {
|
||||
t.Fatalf("to:_ matched %d mail(s): %v", len(toUnderscore), toUnderscore)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 1229/AC6 (words part): cc <my address> and the word "renovatie" in subject or body, combined with the cc filter
|
||||
// from 1228: the mail with it only in to is still excluded.
|
||||
func TestSearchFiltersByCcAndSubjectOrBodyWord(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user