mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JS: Add flow summaries/steps for promises and async/await
This commit is contained in:
@@ -6,7 +6,9 @@ import javascript
|
||||
private import dataflow.internal.StepSummary
|
||||
|
||||
/**
|
||||
* A definition of a `Promise` object.
|
||||
* A call to the `Promise` constructor, such as `new Promise((resolve, reject) => { ... })`.
|
||||
*
|
||||
* This includes calls to the built-in `Promise` constructor as well as promise implementations from known libraries, such as `bluebird`.
|
||||
*/
|
||||
abstract class PromiseDefinition extends DataFlow::SourceNode {
|
||||
/** Gets the executor function of this promise object. */
|
||||
@@ -196,6 +198,8 @@ module Promises {
|
||||
|
||||
override string getAProperty() { result = [valueProp(), errorProp()] }
|
||||
}
|
||||
|
||||
predicate promiseConstructorRef = getAPromiseObject/0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +271,7 @@ private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
* These steps are for `await p`, `new Promise()`, `Promise.resolve()`,
|
||||
* `Promise.then()`, `Promise.catch()`, and `Promise.finally()`.
|
||||
*/
|
||||
private class PromiseStep extends PreCallGraphStep {
|
||||
private class PromiseStep extends LegacyPreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
PromiseFlow::loadStep(obj, element, prop)
|
||||
}
|
||||
@@ -459,7 +463,7 @@ module PromiseFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseTaintStep extends TaintTracking::SharedTaintStep {
|
||||
private class PromiseTaintStep extends TaintTracking::LegacyTaintStep {
|
||||
override predicate promiseStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// from `x` to `new Promise((res, rej) => res(x))`
|
||||
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
|
||||
@@ -530,7 +534,7 @@ private module AsyncReturnSteps {
|
||||
/**
|
||||
* A data-flow step for ordinary and exceptional returns from async functions.
|
||||
*/
|
||||
private class AsyncReturn extends PreCallGraphStep {
|
||||
private class AsyncReturn extends LegacyPreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
|
||||
// ordinary return
|
||||
@@ -548,7 +552,7 @@ private module AsyncReturnSteps {
|
||||
/**
|
||||
* A data-flow step for ordinary return from an async function in a taint configuration.
|
||||
*/
|
||||
private class AsyncTaintReturn extends TaintTracking::SharedTaintStep {
|
||||
private class AsyncTaintReturn extends TaintTracking::LegacyTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Function f |
|
||||
f.isAsync() and
|
||||
@@ -665,7 +669,7 @@ private module ClosurePromise {
|
||||
/**
|
||||
* Taint steps through closure promise methods.
|
||||
*/
|
||||
private class ClosurePromiseTaintStep extends TaintTracking::SharedTaintStep {
|
||||
private class ClosurePromiseTaintStep extends TaintTracking::LegacyTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// static methods in goog.Promise
|
||||
exists(DataFlow::CallNode call, string name |
|
||||
@@ -699,7 +703,7 @@ private module DynamicImportSteps {
|
||||
* let Foo = await import('./foo');
|
||||
* ```
|
||||
*/
|
||||
class DynamicImportStep extends PreCallGraphStep {
|
||||
class DynamicImportStep extends LegacyPreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
exists(DynamicImportExpr imprt |
|
||||
pred = imprt.getImportedModule().getAnExportedValue("default") and
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
private import AmbiguousCoreMethods
|
||||
private import Arrays2
|
||||
private import AsyncAwait
|
||||
private import Promises2
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Contains flow steps to model flow through `async` functions and the `await` operator.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.internal.DataFlowNode
|
||||
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
|
||||
private import semmle.javascript.dataflow.internal.DataFlowPrivate
|
||||
|
||||
/**
|
||||
* Steps modelling flow in an `async` function.
|
||||
*
|
||||
* Note about promise-coercion and flattening:
|
||||
* - `await` preserves non-promise values, e.g. `await "foo"` is just `"foo"`.
|
||||
* - `return` preserves existing promise values, and boxes other values in a promise.
|
||||
*
|
||||
* We rely on `expectsContent` and `clearsContent` to handle coercion/flattening without risk of creating a nested promise object.
|
||||
*
|
||||
* The following is a brief overview of the steps we generate:
|
||||
* ```js
|
||||
* async function foo() {
|
||||
* await x; // x --- READ[promise-value] ---> await x
|
||||
* await x; // x --- VALUE -----------------> await x (has clearsContent)
|
||||
* await x; // x --- READ[promise-error] ---> exception target
|
||||
*
|
||||
* return x; // x --- VALUE --> return node (has expectsContent)
|
||||
* return x; // x --- VALUE --> synthetic node (clearsContent) --- STORE[promise-value] --> return node
|
||||
*
|
||||
* // exceptional return node --> STORE[promise-error] --> return node
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class AsyncAwait extends AdditionalFlowInternal {
|
||||
override predicate needsSynthesizedNode(AstNode node, string tag, DataFlowCallable container) {
|
||||
// We synthesize a clearsContent node to contain the values that need to be boxed in a promise before returning
|
||||
node.(Function).isAsync() and
|
||||
container.asSourceCallable() = node and
|
||||
tag = "async-raw-return"
|
||||
}
|
||||
|
||||
override predicate clearsContent(DataFlow::Node node, DataFlow::ContentSet contents) {
|
||||
node = getSynthesizedNode(_, "async-raw-return") and
|
||||
contents = DataFlow::ContentSet::promiseFilter()
|
||||
or
|
||||
// The result of 'await' cannot be a promise. This is needed for the local flow step into 'await'
|
||||
node.asExpr() instanceof AwaitExpr and
|
||||
contents = DataFlow::ContentSet::promiseFilter()
|
||||
}
|
||||
|
||||
override predicate expectsContent(DataFlow::Node node, DataFlow::ContentSet contents) {
|
||||
// The final return value must be a promise. This is needed for the local flow step into the return node.
|
||||
exists(Function f |
|
||||
f.isAsync() and
|
||||
node = TFunctionReturnNode(f) and
|
||||
contents = DataFlow::ContentSet::promiseFilter()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(AwaitExpr await |
|
||||
// Allow non-promise values to propagate through await.
|
||||
pred = await.getOperand().flow() and
|
||||
succ = await.flow() // clears promise-content
|
||||
)
|
||||
or
|
||||
exists(Function f |
|
||||
// To avoid creating a nested promise, flow to two different nodes which only permit promises/non-promises respectively
|
||||
f.isAsync() and
|
||||
pred = f.getAReturnedExpr().flow()
|
||||
|
|
||||
succ = getSynthesizedNode(f, "async-raw-return") // clears promise-content
|
||||
or
|
||||
succ = TFunctionReturnNode(f) // expects promise-content
|
||||
)
|
||||
}
|
||||
|
||||
override predicate readStep(
|
||||
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
|
||||
) {
|
||||
exists(AwaitExpr await | pred = await.getOperand().flow() |
|
||||
contents = DataFlow::ContentSet::promiseValue() and
|
||||
succ = await.flow()
|
||||
or
|
||||
contents = DataFlow::ContentSet::promiseError() and
|
||||
succ = await.getExceptionTarget()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate storeStep(
|
||||
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
|
||||
) {
|
||||
exists(Function f | f.isAsync() |
|
||||
// Box returned non-promise values in a promise
|
||||
pred = getSynthesizedNode(f, "async-raw-return") and
|
||||
contents = DataFlow::ContentSet::promiseValue() and
|
||||
succ = TFunctionReturnNode(f)
|
||||
or
|
||||
// Store thrown exceptions in promise-error
|
||||
pred = TExceptionalFunctionReturnNode(f) and
|
||||
contents = DataFlow::ContentSet::promiseError() and
|
||||
succ = TFunctionReturnNode(f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -32,3 +32,20 @@ abstract class FunctionalPackageSummary extends SummarizedCallable {
|
||||
result = API::moduleImport(this.getAPackageName()).getAnInvocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a content from a set of contents that together represent all valid array indices.
|
||||
*
|
||||
* This can be used to generate flow summaries that should preserve precise array indices,
|
||||
* in cases where `WithArrayElement` is not sufficient.
|
||||
*/
|
||||
string getAnArrayContent() {
|
||||
// Values stored at a known, small index
|
||||
result = "ArrayElement[" + getAPreciseArrayIndex() + "!]"
|
||||
or
|
||||
// Values stored at a known, but large index
|
||||
result = "ArrayElement[" + (getMaxPreciseArrayIndex() + 1) + "..]"
|
||||
or
|
||||
// Values stored at an unknown index
|
||||
result = "ArrayElement[?]"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* Contains flow summaries and steps modelling flow through `Promise` objects.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.FlowSummary
|
||||
private import FlowSummaryUtil
|
||||
|
||||
private DataFlow::SourceNode promiseConstructorRef() {
|
||||
result = Promises::promiseConstructorRef()
|
||||
or
|
||||
result = DataFlow::moduleImport("bluebird")
|
||||
or
|
||||
result = DataFlow::moduleMember(["q", "kew", "bluebird"], "Promise") // note: bluebird.Promise == bluebird
|
||||
or
|
||||
result = Closure::moduleImport("goog.Promise")
|
||||
}
|
||||
|
||||
//
|
||||
// Note that the 'Awaited' token has a special interpretation.
|
||||
// See a write-up here: https://github.com/github/codeql-javascript-team/issues/423
|
||||
//
|
||||
private class PromiseConstructor extends SummarizedCallable {
|
||||
PromiseConstructor() { this = "new Promise()" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
// Disabled for now. The field-flow branch limit will be negatively affected by having
|
||||
// calls to multiple variants of `new Promise()`.
|
||||
none()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
// TODO: when FlowSummaryImpl.qll supports these summaries, remove the workaround in PromiseConstructorWorkaround
|
||||
// resolve(value)
|
||||
input = "Argument[0].Parameter[0].Argument[0]" and output = "ReturnValue.Awaited"
|
||||
or
|
||||
// reject(value)
|
||||
input = "Argument[0].Parameter[1].Argument[0]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
// throw from executor
|
||||
input = "Argument[0].ReturnValue[exception]" and output = "ReturnValue.Awaited[error]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A workaround to the `PromiseConstructor`, to be used until FlowSummaryImpl.qll has sufficient support
|
||||
* for callbacks.
|
||||
*/
|
||||
module PromiseConstructorWorkaround {
|
||||
class ResolveSummary extends SummarizedCallable {
|
||||
ResolveSummary() { this = "new Promise() resolve callback" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result =
|
||||
promiseConstructorRef().getAnInstantiation().getCallback(0).getParameter(0).getACall()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0]" and
|
||||
output = "Argument[function].Member[resolve-value]"
|
||||
}
|
||||
}
|
||||
|
||||
class RejectCallback extends SummarizedCallable {
|
||||
RejectCallback() { this = "new Promise() reject callback" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result =
|
||||
promiseConstructorRef().getAnInstantiation().getCallback(0).getParameter(1).getACall()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0]" and
|
||||
output = "Argument[function].Member[reject-value]"
|
||||
}
|
||||
}
|
||||
|
||||
class ConstructorSummary extends SummarizedCallable {
|
||||
ConstructorSummary() { this = "new Promise() workaround" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAnInstantiation()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].Parameter[0].Member[resolve-value]" and
|
||||
output = "ReturnValue.Awaited"
|
||||
or
|
||||
input = "Argument[0].Parameter[1].Member[reject-value]" and
|
||||
output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[0].ReturnValue[exception]" and
|
||||
output = "ReturnValue.Awaited[error]"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseThen2Arguments extends SummarizedCallable {
|
||||
PromiseThen2Arguments() { this = "Promise#then() with 2 arguments" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result.getMethodName() = "then" and
|
||||
result.getNumArgument() = 2
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0,1].ReturnValue" and output = "ReturnValue.Awaited"
|
||||
or
|
||||
input = "Argument[0,1].ReturnValue[exception]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[this].Awaited[value]" and output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[this].Awaited[error]" and output = "Argument[1].Parameter[0]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseThen1Argument extends SummarizedCallable {
|
||||
PromiseThen1Argument() { this = "Promise#then() with 1 argument" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result.getMethodName() = "then" and
|
||||
result.getNumArgument() = 1
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].ReturnValue" and output = "ReturnValue.Awaited"
|
||||
or
|
||||
input = "Argument[0].ReturnValue[exception]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[this].Awaited[value]" and output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[this].WithAwaited[error]" and output = "ReturnValue"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseCatch extends SummarizedCallable {
|
||||
PromiseCatch() { this = "Promise#catch()" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "catch" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].ReturnValue" and output = "ReturnValue.Awaited"
|
||||
or
|
||||
input = "Argument[0].ReturnValue[exception]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[this].Awaited[value]" and output = "ReturnValue.Awaited[value]"
|
||||
or
|
||||
input = "Argument[this].Awaited[error]" and output = "Argument[0].Parameter[0]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseFinally extends SummarizedCallable {
|
||||
PromiseFinally() { this = "Promise#finally()" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "finally" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].ReturnValue.Awaited[error]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[0].ReturnValue[exception]" and output = "ReturnValue.Awaited[error]"
|
||||
or
|
||||
input = "Argument[this].WithAwaited[value,error]" and output = "ReturnValue"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseResolve extends SummarizedCallable {
|
||||
PromiseResolve() { this = "Promise.resolve()" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall("resolve")
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue.Awaited"
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseReject extends SummarizedCallable {
|
||||
PromiseReject() { this = "Promise.reject()" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall("reject")
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue.Awaited[error]"
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseAll extends SummarizedCallable {
|
||||
PromiseAll() { this = "Promise.all()" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall("all")
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
exists(string content | content = getAnArrayContent() |
|
||||
input = "Argument[0]." + content + ".Awaited" and
|
||||
output = "ReturnValue.Awaited[value]." + content
|
||||
)
|
||||
or
|
||||
preservesValue = true and
|
||||
input = "Argument[0].ArrayElement.WithAwaited[error]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseAnyLike extends SummarizedCallable {
|
||||
PromiseAnyLike() { this = "Promise.any() or Promise.race()" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall(["any", "race", "firstFulfilled"])
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0].ArrayElement" and
|
||||
output = "ReturnValue.Awaited"
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseAllSettled extends SummarizedCallable {
|
||||
PromiseAllSettled() { this = "Promise.allSettled()" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall("allSettled")
|
||||
or
|
||||
result = DataFlow::moduleImport("promise.allsettled").getACall()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
exists(string content | content = getAnArrayContent() |
|
||||
input = "Argument[0]." + content + ".Awaited" and
|
||||
output = "ReturnValue.Awaited[value]." + content + ".Member[value]"
|
||||
or
|
||||
input = "Argument[0]." + content + ".Awaited[error]" and
|
||||
output = "ReturnValue.Awaited[value]." + content + ".Member[reason]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class BluebirdMapSeries extends SummarizedCallable {
|
||||
BluebirdMapSeries() { this = "bluebird.mapSeries" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall("mapSeries")
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].Awaited.ArrayElement.Awaited" and
|
||||
output = "Argument[1].Parameter[0]"
|
||||
or
|
||||
input = "Argument[0].Awaited.ArrayElement.WithAwaited[error]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[0].WithAwaited[error]" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[1].ReturnValue.Awaited" and
|
||||
output = "ReturnValue.Awaited.ArrayElement"
|
||||
or
|
||||
input = "Argument[1].ReturnValue.WithAwaited[error]" and
|
||||
output = "ReturnValue"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - `Promise.withResolvers`, a method pending standardization,
|
||||
* - `goog.Closure.withResolver()` (non-plural spelling)
|
||||
* - `bluebird.Promise.defer()`
|
||||
*/
|
||||
private class PromiseWithResolversLike extends SummarizedCallable {
|
||||
PromiseWithResolversLike() { this = "Promise.withResolvers()" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = promiseConstructorRef().getAMemberCall(["withResolver", "withResolvers", "defer"])
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
// TODO: not currently supported by FlowSummaryImpl.qll
|
||||
input = "ReturnValue.Member[resolve].Argument[0]" and
|
||||
output = "ReturnValue.Member[promise].Awaited"
|
||||
or
|
||||
input = "ReturnValue.Member[reject].Argument[0]" and
|
||||
output = "ReturnValue.Member[promise].Awaited[error]"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user