From 58a3a0b7d42e7a91d332e11715f45e36c0a7bd56 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 3 Dec 2022 18:42:06 +0900 Subject: [PATCH] forkbomb DOS mitigation --- .../src/remote/activitypub/kernel/update/index.ts | 8 ++++---- .../src/remote/activitypub/models/person.ts | 8 ++++---- .../src/remote/activitypub/models/question.ts | 8 ++++---- .../backend/src/remote/activitypub/resolver.ts | 15 ++++++++++----- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 9e8a81bb39..a142cb46ef 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -10,7 +10,7 @@ import { updatePerson } from '../../models/person.js'; */ export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { - return `skip: invalid actor`; + return 'skip: invalid actor'; } apLogger.debug('Update'); @@ -24,10 +24,10 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise console.log(e)); - return `ok: Question updated`; + await updateQuestion(object, resolver).catch(e => console.log(e)); + return 'ok: Question updated'; } else { return `skip: Unknown type: ${getApType(object)}`; } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 6097e3b6ed..5ef04588e9 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -271,7 +271,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise logger.error(err)); + await updateFeatured(user!.id, resolver).catch(err => logger.error(err)); return user!; } @@ -384,7 +384,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), }); - await updateFeatured(exist.id).catch(err => logger.error(err)); + await updateFeatured(exist.id, resolver).catch(err => logger.error(err)); } /** @@ -462,14 +462,14 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined) return { fields, services }; } -export async function updateFeatured(userId: User['id']) { +export async function updateFeatured(userId: User['id'], resolver?: Resolver) { const user = await Users.findOneByOrFail({ id: userId }); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; logger.info(`Updating the featured: ${user.uri}`); - const resolver = new Resolver(); + if (resolver == null) resolver = new Resolver(); // Resolve to (Ordered)Collection Object const collection = await resolver.resolveCollection(user.featured); diff --git a/packages/backend/src/remote/activitypub/models/question.ts b/packages/backend/src/remote/activitypub/models/question.ts index f0321fdf2f..57070fb1e7 100644 --- a/packages/backend/src/remote/activitypub/models/question.ts +++ b/packages/backend/src/remote/activitypub/models/question.ts @@ -1,9 +1,9 @@ import config from '@/config/index.js'; +import { Notes, Polls } from '@/models/index.js'; +import { IPoll } from '@/models/entities/poll.js'; import Resolver from '../resolver.js'; import { IObject, IQuestion, isQuestion } from '../type.js'; import { apLogger } from '../logger.js'; -import { Notes, Polls } from '@/models/index.js'; -import { IPoll } from '@/models/entities/poll.js'; export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise { if (resolver == null) resolver = new Resolver(); @@ -40,7 +40,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver * @param uri URI of AP Question object * @returns true if updated */ -export async function updateQuestion(value: any) { +export async function updateQuestion(value: any, resolver?: Resolver) { const uri = typeof value === 'string' ? value : value.id; // URIがこのサーバーを指しているならスキップ @@ -55,7 +55,7 @@ export async function updateQuestion(value: any) { //#endregion // resolve new Question object - const resolver = new Resolver(); + if (resolver == null) resolver = new Resolver(); const question = await resolver.resolve(value) as IQuestion; apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 2f9af43c0c..ad0df0c97a 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -4,10 +4,7 @@ import { ILocalUser } from '@/models/entities/user.js'; import { getInstanceActor } from '@/services/instance-actor.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { extractDbHost, isSelfHost } from '@/misc/convert-host.js'; -import { signedGet } from './request.js'; -import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js'; -import { parseUri } from './db-resolver.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { renderPerson } from '@/remote/activitypub/renderer/person.js'; @@ -15,12 +12,16 @@ import renderQuestion from '@/remote/activitypub/renderer/question.js'; import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; +import { parseUri } from './db-resolver.js'; +import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; +import { signedGet } from './request.js'; export default class Resolver { private history: Set; private user?: ILocalUser; + private recursionLimit?: number; - constructor() { + constructor(recursionLimit = 100) { this.history = new Set(); } @@ -60,6 +61,10 @@ export default class Resolver { throw new Error('cannot resolve already resolved one'); } + if (this.recursionLimit && this.history.size > this.recursionLimit) { + throw new Error('hit recursion limit'); + } + this.history.add(value); const host = extractDbHost(value); @@ -123,7 +128,7 @@ export default class Resolver { if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); return Promise.all( - [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })) + [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })), ) .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url))); default: