CVE-2022-4767: Uncontrolled Resource Consumption (CWE-400)
value: Denial of Service in GitHub repository usememos/memos prior to 0.9.1.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
CVSS v3 base score: 7.5
Published: 2022-12-27T15:15Z
Repository: https://github.com/usememos/memos
Language: YAML
Files changed: .github/workflows/backend-tests-default.yml, .github/workflows/backend-tests.yml, .github/workflows/frontend-tests-default.yml, .github/workflows/frontend-tests.yml, api/user.go, server/user.go
Commit date: 2022-12-27 21:51:43 +0800
Commit message: chore: update userinfo validator (#868)

* chore: update userinfo validator

* chore: update actions

* chore: update
Diff: diff --git a/.github/workflows/backend-tests-default.yml b/.github/workflows/backend-tests-default.yml
new file mode 100644
index 0000000..79b3ca6
--- /dev/null
+++ b/.github/workflows/backend-tests-default.yml
@@ -0,0 +1,20 @@
+name: Backend Test
+
+on:
+  pull_request:
+    branches:
+      - main
+      - "release/v*.*.*"
+    paths:
+      - "web/**"
+
+jobs:
+  go-static-checks:
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo "Not required"'
+
+  go-tests:
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo "Not required"'
diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml
index b9330cf..01d9dc5 100644
--- a/.github/workflows/backend-tests.yml
+++ b/.github/workflows/backend-tests.yml
@@ -1,14 +1,12 @@
 name: Backend Test
 
 on:
-  push:
+  pull_request:
     branches:
       - main
       - "release/v*.*.*"
-  pull_request:
-    branches: [main]
     paths-ignore:
       - "web/**"
 
 jobs:
   go-static-checks:
diff --git a/.github/workflows/frontend-tests-default.yml b/.github/workflows/frontend-tests-default.yml
new file mode 100644
index 0000000..3aec38a
--- /dev/null
+++ b/.github/workflows/frontend-tests-default.yml
@@ -0,0 +1,25 @@
+name: Frontend Test
+
+on:
+  pull_request:
+    branches:
+      - main
+      - "release/v*.*.*"
+    paths-ignore:
+      - "web/**"
+
+jobs:
+  eslint-checks:
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo "Not required"'
+
+  jest-tests:
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo "Not required"'
+
+  frontend-build:
+    runs-on: ubuntu-latest
+    steps:
+      - run: 'echo "Not required"'
diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml
index 81c31cb..65b5ec2 100644
--- a/.github/workflows/frontend-tests.yml
+++ b/.github/workflows/frontend-tests.yml
@@ -1,14 +1,12 @@
 name: Frontend Test
 
 on:
-  push:
+  pull_request:
     branches:
       - main
       - "release/v*.*.*"
-  pull_request:
-    branches: [main]
     paths:
       - "web/**"
 
 jobs:
   eslint-checks:
diff --git a/api/user.go b/api/user.go
index 529f3fc..1c3b4f9 100644
--- a/api/user.go
+++ b/api/user.go
@@ -1,9 +1,11 @@
 package api
 
 import (
 	"fmt"
+
+	"github.com/usememos/memos/common"
 )
 
 // Role is the type of a role.
 type Role string
 
@@ -59,13 +61,27 @@ type UserCreate struct {
 
 func (create UserCreate) Validate() error {
 	if len(create.Username) < 4 {
 		return fmt.Errorf("username is too short, minimum length is 4")
 	}
+	if len(create.Username) > 32 {
+		return fmt.Errorf("username is too long, maximum length is 32")
+	}
 	if len(create.Password) < 4 {
 		return fmt.Errorf("password is too short, minimum length is 4")
 	}
+	if len(create.Nickname) > 64 {
+		return fmt.Errorf("nickname is too long, maximum length is 64")
+	}
+	if create.Email != "" {
+		if len(create.Email) > 256 {
+			return fmt.Errorf("email is too long, maximum length is 256")
+		}
+		if common.ValidateEmail(create.Email) {
+			return fmt.Errorf("invalid email format")
+		}
+	}
 
 	return nil
 }
 
 type UserPatch struct {
@@ -83,10 +99,35 @@ type UserPatch struct {
 	ResetOpenID  *bool   `json:"resetOpenId"`
 	PasswordHash *string
 	OpenID       *string
 }
 
+func (patch UserPatch) Validate() error {
+	if patch.Username != nil && len(*patch.Username) < 4 {
+		return fmt.Errorf("username is too short, minimum length is 4")
+	}
+	if patch.Username != nil && len(*patch.Username) > 32 {
+		return fmt.Errorf("username is too long, maximum length is 32")
+	}
+	if patch.Password != nil && len(*patch.Password) < 4 {
+		return fmt.Errorf("password is too short, minimum length is 4")
+	}
+	if patch.Nickname != nil && len(*patch.Nickname) > 64 {
+		return fmt.Errorf("nickname is too long, maximum length is 64")
+	}
+	if patch.Email != nil {
+		if len(*patch.Email) > 256 {
+			return fmt.Errorf("email is too long, maximum length is 256")
+		}
+		if common.ValidateEmail(*patch.Email) {
+			return fmt.Errorf("invalid email format")
+		}
+	}
+
+	return nil
+}
+
 type UserFind struct {
 	ID *int `json:"id"`
 
 	// Standard fields
 	RowStatus *RowStatus `json:"rowStatus"`
diff --git a/server/user.go b/server/user.go
index 5adbdfd..76d7658 100644
--- a/server/user.go
+++ b/server/user.go
@@ -196,13 +196,12 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
 			UpdatedTs: &currentTs,
 		}
 		if err := json.NewDecoder(c.Request().Body).Decode(userPatch); err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
 		}
-
-		if userPatch.Email != nil && *userPatch.Email != "" && !common.ValidateEmail(*userPatch.Email) {
-			return echo.NewHTTPError(http.StatusBadRequest, "Invalid email format")
+		if err := userPatch.Validate(); err != nil {
+			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format.").SetInternal(err)
 		}
 
 		if userPatch.Password != nil && *userPatch.Password != "" {
 			passwordHash, err := bcrypt.GenerateFromPassword([]byte(*userPatch.Password), bcrypt.DefaultCost)
 			if err != nil {

Vulnerable code:   push:
  pull_request:
    branches: [main]
  push:
  pull_request:
    branches: [main]

		if userPatch.Email != nil && *userPatch.Email != "" && !common.ValidateEmail(*userPatch.Email) {
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid email format")
Fixed code: name: Backend Test

on:
  pull_request:
    branches:
      - main
      - "release/v*.*.*"
    paths:
      - "web/**"

jobs:
  go-static-checks:
    runs-on: ubuntu-latest
    steps:
      - run: 'echo "Not required"'

  go-tests:
    runs-on: ubuntu-latest
    steps:
      - run: 'echo "Not required"'
  pull_request:
name: Frontend Test

on:
  pull_request:
    branches:
      - main
      - "release/v*.*.*"
    paths-ignore:
      - "web/**"

jobs:
  eslint-checks:
    runs-on: ubuntu-latest
    steps:
      - run: 'echo "Not required"'

  jest-tests:
    runs-on: ubuntu-latest
    steps:
      - run: 'echo "Not required"'

  frontend-build:
    runs-on: ubuntu-latest
    steps:
      - run: 'echo "Not required"'
  pull_request:

	"github.com/usememos/memos/common"
	if len(create.Username) > 32 {
		return fmt.Errorf("username is too long, maximum length is 32")
	}
	if len(create.Nickname) > 64 {
		return fmt.Errorf("nickname is too long, maximum length is 64")
	}
	if create.Email != "" {
		if len(create.Email) > 256 {
			return fmt.Errorf("email is too long, maximum length is 256")
		}
		if common.ValidateEmail(create.Email) {
			return fmt.Errorf("invalid email format")
		}
	}
func (patch UserPatch) Validate() error {
	if patch.Username != nil && len(*patch.Username) < 4 {
		return fmt.Errorf("username is too short, minimum length is 4")
	}
	if patch.Username != nil && len(*patch.Username) > 32 {
		return fmt.Errorf("username is too long, maximum length is 32")
	}
	if patch.Password != nil && len(*patch.Password) < 4 {
		return fmt.Errorf("password is too short, minimum length is 4")
	}
	if patch.Nickname != nil && len(*patch.Nickname) > 64 {
		return fmt.Errorf("nickname is too long, maximum length is 64")
	}
	if patch.Email != nil {
		if len(*patch.Email) > 256 {
			return fmt.Errorf("email is too long, maximum length is 256")
		}
		if common.ValidateEmail(*patch.Email) {
			return fmt.Errorf("invalid email format")
		}
	}

	return nil
}

		if err := userPatch.Validate(); err != nil {
			return echo.NewHTTPError(http.StatusBadRequest, "Invalid user patch format.").SetInternal(err)