mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-03-26 08:13:04 -04:00
One of the security patches released 2026-03-09 [fixed a vulnerability](d1c7b04d09) caused by a misapplication of Go `case` statements, where the implementation would have been correct if Go `case` statements automatically fall through to the next case block, but they do not. This PR adds a semgrep rule which detects any empty `case` statement and raises an error, in order to prevent this coding mistake in the future.
For example, code like this will now trigger a build error:
```go
switch setting.Protocol {
case setting.HTTPUnix:
case setting.FCGI:
case setting.FCGIUnix:
default:
defaultLocalURL := string(setting.Protocol) + "://"
}
```
Example error:
```
cmd/web.go
❯❯❱ semgrep.config.forgejo-switch-empty-case
switch has a case block with no content. This is treated as "break" by Go, but developers may
confuse it for "fallthrough". To fix this error, disambiguate by using "break" or
"fallthrough".
279┆ switch setting.Protocol {
280┆ case setting.HTTPUnix:
281┆ case setting.FCGI:
282┆ case setting.FCGIUnix:
283┆ default:
284┆ defaultLocalURL := string(setting.Protocol) + "://"
285┆ if setting.HTTPAddr == "0.0.0.0" {
286┆ defaultLocalURL += "localhost"
287┆ } else {
288┆ defaultLocalURL += setting.HTTPAddr
```
As described in the error output, this error can be fixed by explicitly listing `break` (the real Go behaviour, to do nothing in the block), or by listing `fallthrough` (if the intent was to fall through).
All existing code triggering this detection has been changed to `break` (or, rarely, irrelevant cases have been removed), which should maintain the same code functionality. While performing this fixup, a light analysis was performed on each case and they *appeared* correct, but with ~65 cases I haven't gone into extreme depth.
Tests are present for the semgrep rule in `.semgrep/tests/go.go`.
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Documentation
- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.
### Release notes
- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11593
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
267 lines
7.2 KiB
Go
267 lines
7.2 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package packages
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
repo_model "forgejo.org/models/repo"
|
|
user_model "forgejo.org/models/user"
|
|
"forgejo.org/modules/json"
|
|
"forgejo.org/modules/packages/alpine"
|
|
"forgejo.org/modules/packages/arch"
|
|
"forgejo.org/modules/packages/cargo"
|
|
"forgejo.org/modules/packages/chef"
|
|
"forgejo.org/modules/packages/composer"
|
|
"forgejo.org/modules/packages/conan"
|
|
"forgejo.org/modules/packages/conda"
|
|
"forgejo.org/modules/packages/container"
|
|
"forgejo.org/modules/packages/cran"
|
|
"forgejo.org/modules/packages/debian"
|
|
"forgejo.org/modules/packages/helm"
|
|
"forgejo.org/modules/packages/maven"
|
|
"forgejo.org/modules/packages/npm"
|
|
"forgejo.org/modules/packages/nuget"
|
|
"forgejo.org/modules/packages/pub"
|
|
"forgejo.org/modules/packages/pypi"
|
|
"forgejo.org/modules/packages/rpm"
|
|
"forgejo.org/modules/packages/rubygems"
|
|
"forgejo.org/modules/packages/swift"
|
|
"forgejo.org/modules/packages/vagrant"
|
|
"forgejo.org/modules/util"
|
|
|
|
"github.com/hashicorp/go-version"
|
|
)
|
|
|
|
// PackagePropertyList is a list of package properties
|
|
type PackagePropertyList []*PackageProperty
|
|
|
|
// GetByName gets the first property value with the specific name
|
|
func (l PackagePropertyList) GetByName(name string) string {
|
|
for _, pp := range l {
|
|
if pp.Name == name {
|
|
return pp.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// PackageDescriptor describes a package
|
|
type PackageDescriptor struct {
|
|
Package *Package
|
|
Owner *user_model.User
|
|
Repository *repo_model.Repository
|
|
Version *PackageVersion
|
|
SemVer *version.Version
|
|
Creator *user_model.User
|
|
PackageProperties PackagePropertyList
|
|
VersionProperties PackagePropertyList
|
|
Metadata any
|
|
Files []*PackageFileDescriptor
|
|
}
|
|
|
|
// PackageFileDescriptor describes a package file
|
|
type PackageFileDescriptor struct {
|
|
File *PackageFile
|
|
Blob *PackageBlob
|
|
Properties PackagePropertyList
|
|
}
|
|
|
|
// PackageWebLink returns the relative package web link
|
|
func (pd *PackageDescriptor) PackageWebLink() string {
|
|
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
|
}
|
|
|
|
// VersionWebLink returns the relative package version web link
|
|
func (pd *PackageDescriptor) VersionWebLink() string {
|
|
return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion))
|
|
}
|
|
|
|
// PackageHTMLURL returns the absolute package HTML URL
|
|
func (pd *PackageDescriptor) PackageHTMLURL() string {
|
|
return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName))
|
|
}
|
|
|
|
// VersionHTMLURL returns the absolute package version HTML URL
|
|
func (pd *PackageDescriptor) VersionHTMLURL() string {
|
|
return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion))
|
|
}
|
|
|
|
// CalculateBlobSize returns the total blobs size in bytes
|
|
func (pd *PackageDescriptor) CalculateBlobSize() int64 {
|
|
size := int64(0)
|
|
for _, f := range pd.Files {
|
|
size += f.Blob.Size
|
|
}
|
|
return size
|
|
}
|
|
|
|
// GetPackageDescriptor gets the package description for a version
|
|
func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) {
|
|
p, err := GetPackageByID(ctx, pv.PackageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
o, err := user_model.GetUserByID(ctx, p.OwnerID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var repository *repo_model.Repository
|
|
if p.RepoID > 0 {
|
|
repository, err = repo_model.GetRepositoryByID(ctx, p.RepoID)
|
|
if err != nil && !repo_model.IsErrRepoNotExist(err) {
|
|
return nil, err
|
|
}
|
|
}
|
|
creator, err := user_model.GetUserByID(ctx, pv.CreatorID)
|
|
if err != nil {
|
|
if errors.Is(err, util.ErrNotExist) {
|
|
creator = user_model.NewGhostUser()
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
var semVer *version.Version
|
|
if p.SemverCompatible {
|
|
semVer, err = version.NewVersion(pv.Version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pfs, err := GetFilesByVersionID(ctx, pv.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pfds, err := GetPackageFileDescriptors(ctx, pfs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var metadata any
|
|
switch p.Type {
|
|
case TypeAlpine:
|
|
metadata = &alpine.VersionMetadata{}
|
|
case TypeArch:
|
|
metadata = &arch.VersionMetadata{}
|
|
case TypeCargo:
|
|
metadata = &cargo.Metadata{}
|
|
case TypeChef:
|
|
metadata = &chef.Metadata{}
|
|
case TypeComposer:
|
|
metadata = &composer.Metadata{}
|
|
case TypeConan:
|
|
metadata = &conan.Metadata{}
|
|
case TypeConda:
|
|
metadata = &conda.VersionMetadata{}
|
|
case TypeContainer:
|
|
metadata = &container.Metadata{}
|
|
case TypeCran:
|
|
metadata = &cran.Metadata{}
|
|
case TypeDebian:
|
|
metadata = &debian.Metadata{}
|
|
case TypeGeneric:
|
|
// generic packages have no metadata
|
|
break
|
|
case TypeGo:
|
|
// go packages have no metadata
|
|
break
|
|
case TypeHelm:
|
|
metadata = &helm.Metadata{}
|
|
case TypeNuGet:
|
|
metadata = &nuget.Metadata{}
|
|
case TypeNpm:
|
|
metadata = &npm.Metadata{}
|
|
case TypeMaven:
|
|
metadata = &maven.Metadata{}
|
|
case TypePub:
|
|
metadata = &pub.Metadata{}
|
|
case TypePyPI:
|
|
metadata = &pypi.Metadata{}
|
|
case TypeRpm:
|
|
metadata = &rpm.VersionMetadata{}
|
|
case TypeAlt:
|
|
metadata = &rpm.VersionMetadata{}
|
|
case TypeRubyGems:
|
|
metadata = &rubygems.Metadata{}
|
|
case TypeSwift:
|
|
metadata = &swift.Metadata{}
|
|
case TypeVagrant:
|
|
metadata = &vagrant.Metadata{}
|
|
default:
|
|
panic(fmt.Sprintf("unknown package type: %s", string(p.Type)))
|
|
}
|
|
if metadata != nil {
|
|
if err := json.Unmarshal([]byte(pv.MetadataJSON), &metadata); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &PackageDescriptor{
|
|
Package: p,
|
|
Owner: o,
|
|
Repository: repository,
|
|
Version: pv,
|
|
SemVer: semVer,
|
|
Creator: creator,
|
|
PackageProperties: PackagePropertyList(pps),
|
|
VersionProperties: PackagePropertyList(pvps),
|
|
Metadata: metadata,
|
|
Files: pfds,
|
|
}, nil
|
|
}
|
|
|
|
// GetPackageFileDescriptor gets a package file descriptor for a package file
|
|
func GetPackageFileDescriptor(ctx context.Context, pf *PackageFile) (*PackageFileDescriptor, error) {
|
|
pb, err := GetBlobByID(ctx, pf.BlobID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pfps, err := GetProperties(ctx, PropertyTypeFile, pf.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &PackageFileDescriptor{
|
|
pf,
|
|
pb,
|
|
PackagePropertyList(pfps),
|
|
}, nil
|
|
}
|
|
|
|
// GetPackageFileDescriptors gets the package file descriptors for the package files
|
|
func GetPackageFileDescriptors(ctx context.Context, pfs []*PackageFile) ([]*PackageFileDescriptor, error) {
|
|
pfds := make([]*PackageFileDescriptor, 0, len(pfs))
|
|
for _, pf := range pfs {
|
|
pfd, err := GetPackageFileDescriptor(ctx, pf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pfds = append(pfds, pfd)
|
|
}
|
|
return pfds, nil
|
|
}
|
|
|
|
// GetPackageDescriptors gets the package descriptions for the versions
|
|
func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*PackageDescriptor, error) {
|
|
pds := make([]*PackageDescriptor, 0, len(pvs))
|
|
for _, pv := range pvs {
|
|
pd, err := GetPackageDescriptor(ctx, pv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pds = append(pds, pd)
|
|
}
|
|
return pds, nil
|
|
}
|