fix(server): "forkbomb" DOS mitigation (#9247)
* Add recursion limit to resolver * Use shared resolver in featured and question * Changelog * Changelog fix * Update CHANGELOG.md Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * Add host to recursion limit error message Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
This commit is contained in:
		
							parent
							
								
									5decad9cf1
								
							
						
					
					
						commit
						66513b9893
					
				
					 5 changed files with 14 additions and 8 deletions
				
			
		|  | @ -4,7 +4,7 @@ | ||||||
| ### Improvements | ### Improvements | ||||||
| 
 | 
 | ||||||
| ### Bugfixes | ### Bugfixes | ||||||
| -  | - | ||||||
| 
 | 
 | ||||||
| You should also include the user name that made the change. | You should also include the user name that made the change. | ||||||
| --> | --> | ||||||
|  | @ -25,6 +25,7 @@ You should also include the user name that made the change. | ||||||
| - Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468 | - Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468 | ||||||
| - Server: Bug fix for Pinned Users lookup on instance @squidicuzz | - Server: Bug fix for Pinned Users lookup on instance @squidicuzz | ||||||
| - Client: インスタンスティッカーのfaviconを読み込む際に偽サイト警告が出ることがあるのを修正 @syuilo | - Client: インスタンスティッカーのfaviconを読み込む際に偽サイト警告が出ることがあるのを修正 @syuilo | ||||||
|  | - Server: Mitigate AP reference chain DoS vector @skehmatics | ||||||
| 
 | 
 | ||||||
| ## 12.119.0 (2022/09/10) | ## 12.119.0 (2022/09/10) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -731,7 +731,7 @@ export class ApInboxService { | ||||||
| 			await this.apPersonService.updatePerson(actor.uri!, resolver, object); | 			await this.apPersonService.updatePerson(actor.uri!, resolver, object); | ||||||
| 			return 'ok: Person updated'; | 			return 'ok: Person updated'; | ||||||
| 		} else if (getApType(object) === 'Question') { | 		} else if (getApType(object) === 'Question') { | ||||||
| 			await this.apQuestionService.updateQuestion(object).catch(err => console.error(err)); | 			await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); | ||||||
| 			return 'ok: Question updated'; | 			return 'ok: Question updated'; | ||||||
| 		} else { | 		} else { | ||||||
| 			return `skip: Unknown type: ${getApType(object)}`; | 			return `skip: Unknown type: ${getApType(object)}`; | ||||||
|  |  | ||||||
|  | @ -76,6 +76,7 @@ export class Resolver { | ||||||
| 		private httpRequestService: HttpRequestService, | 		private httpRequestService: HttpRequestService, | ||||||
| 		private apRendererService: ApRendererService, | 		private apRendererService: ApRendererService, | ||||||
| 		private apDbResolverService: ApDbResolverService, | 		private apDbResolverService: ApDbResolverService, | ||||||
|  | 		private recursionLimit = 100 | ||||||
| 	) { | 	) { | ||||||
| 		this.history = new Set(); | 		this.history = new Set(); | ||||||
| 	} | 	} | ||||||
|  | @ -116,6 +117,10 @@ export class Resolver { | ||||||
| 			throw new Error('cannot resolve already resolved one'); | 			throw new Error('cannot resolve already resolved one'); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if (this.history.size > this.recursionLimit) { | ||||||
|  | 			throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		this.history.add(value); | 		this.history.add(value); | ||||||
| 
 | 
 | ||||||
| 		const host = this.utilityService.extractDbHost(value); | 		const host = this.utilityService.extractDbHost(value); | ||||||
|  |  | ||||||
|  | @ -390,7 +390,7 @@ export class ApPersonService implements OnModuleInit { | ||||||
| 	}); | 	}); | ||||||
| 	//#endregion
 | 	//#endregion
 | ||||||
| 
 | 
 | ||||||
| 	await this.updateFeatured(user!.id).catch(err => this.logger.error(err)); | 	await this.updateFeatured(user!.id, resolver).catch(err => this.logger.error(err)); | ||||||
| 
 | 
 | ||||||
| 	return user!; | 	return user!; | ||||||
| 	} | 	} | ||||||
|  | @ -503,7 +503,7 @@ export class ApPersonService implements OnModuleInit { | ||||||
| 			followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), | 			followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined), | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		await this.updateFeatured(exist.id).catch(err => this.logger.error(err)); | 		await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
|  | @ -551,14 +551,14 @@ export class ApPersonService implements OnModuleInit { | ||||||
| 		return { fields, services }; | 		return { fields, services }; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public async updateFeatured(userId: User['id']) { | 	public async updateFeatured(userId: User['id'], resolver?: Resolver) { | ||||||
| 		const user = await this.usersRepository.findOneByOrFail({ id: userId }); | 		const user = await this.usersRepository.findOneByOrFail({ id: userId }); | ||||||
| 		if (!this.userEntityService.isRemoteUser(user)) return; | 		if (!this.userEntityService.isRemoteUser(user)) return; | ||||||
| 		if (!user.featured) return; | 		if (!user.featured) return; | ||||||
| 
 | 
 | ||||||
| 		this.logger.info(`Updating the featured: ${user.uri}`); | 		this.logger.info(`Updating the featured: ${user.uri}`); | ||||||
| 
 | 
 | ||||||
| 		const resolver = this.apResolverService.createResolver(); | 		if (resolver == null) resolver = this.apResolverService.createResolver(); | ||||||
| 
 | 
 | ||||||
| 		// Resolve to (Ordered)Collection Object
 | 		// Resolve to (Ordered)Collection Object
 | ||||||
| 		const collection = await resolver.resolveCollection(user.featured); | 		const collection = await resolver.resolveCollection(user.featured); | ||||||
|  |  | ||||||
|  | @ -65,7 +65,7 @@ export class ApQuestionService { | ||||||
| 	 * @param uri URI of AP Question object | 	 * @param uri URI of AP Question object | ||||||
| 	 * @returns true if updated | 	 * @returns true if updated | ||||||
| 	 */ | 	 */ | ||||||
| 	public async updateQuestion(value: any) { | 	public async updateQuestion(value: any, resolver?: Resolver) { | ||||||
| 		const uri = typeof value === 'string' ? value : value.id; | 		const uri = typeof value === 'string' ? value : value.id; | ||||||
| 
 | 
 | ||||||
| 		// URIがこのサーバーを指しているならスキップ
 | 		// URIがこのサーバーを指しているならスキップ
 | ||||||
|  | @ -80,7 +80,7 @@ export class ApQuestionService { | ||||||
| 		//#endregion
 | 		//#endregion
 | ||||||
| 
 | 
 | ||||||
| 		// resolve new Question object
 | 		// resolve new Question object
 | ||||||
| 		const resolver = this.apResolverService.createResolver(); | 		if (resolver == null) resolver = this.apResolverService.createResolver(); | ||||||
| 		const question = await resolver.resolve(value) as IQuestion; | 		const question = await resolver.resolve(value) as IQuestion; | ||||||
| 		this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); | 		this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue